fix(session): bound the SID→UDID dict with FIFO eviction

The map used to grow unbounded over the process's lifetime — every fresh
signup added an entry that was never reclaimed. Long-running dev hosts
(or any future emulator deployment that doesn't restart often) would
gradually leak memory. Cap at 10k entries by default with a simple FIFO
eviction queue; re-stores of the same SID don't grow the queue.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-05-28 21:13:47 -04:00
parent 91412ff821
commit 177b4925a1
2 changed files with 89 additions and 3 deletions

View File

@@ -35,4 +35,54 @@ public class ShadowverseSessionServiceTests
Assert.That(svc.GetUdidFromSessionId("dc4aac79d35fe15dfb6262e0071bb03c"), Is.EqualTo(udid));
}
[Test]
public void StoreUdidForSessionId_evicts_oldest_when_cap_exceeded()
{
// Cap=3, insert 5 distinct SIDs; the two earliest must be evicted.
var svc = new ShadowverseSessionService(maxEntries: 3);
var udid = new System.Guid("62747917-93bc-454c-abb4-ef423b3c9317");
svc.StoreUdidForSessionId("sid-1", udid);
svc.StoreUdidForSessionId("sid-2", udid);
svc.StoreUdidForSessionId("sid-3", udid);
svc.StoreUdidForSessionId("sid-4", udid);
svc.StoreUdidForSessionId("sid-5", udid);
Assert.That(svc.GetUdidFromSessionId("sid-1"), Is.Null, "Oldest entry must be evicted.");
Assert.That(svc.GetUdidFromSessionId("sid-2"), Is.Null, "Second-oldest entry must be evicted.");
Assert.That(svc.GetUdidFromSessionId("sid-3"), Is.EqualTo(udid));
Assert.That(svc.GetUdidFromSessionId("sid-4"), Is.EqualTo(udid));
Assert.That(svc.GetUdidFromSessionId("sid-5"), Is.EqualTo(udid));
}
[Test]
public void StoreUdidForSessionId_re_storing_same_sid_does_not_grow_queue()
{
// Cap=2. Store sid-A, then re-store sid-A many times, then store sid-B and sid-C.
// The re-stores must NOT count toward the cap — sid-A should still resolve after
// sid-B and sid-C land, because only two distinct SIDs are tracked.
var svc = new ShadowverseSessionService(maxEntries: 2);
var udid = new System.Guid("62747917-93bc-454c-abb4-ef423b3c9317");
svc.StoreUdidForSessionId("sid-A", udid);
for (int i = 0; i < 20; i++) svc.StoreUdidForSessionId("sid-A", udid);
svc.StoreUdidForSessionId("sid-B", udid);
Assert.That(svc.GetUdidFromSessionId("sid-A"), Is.EqualTo(udid), "sid-A must still resolve after re-stores.");
Assert.That(svc.GetUdidFromSessionId("sid-B"), Is.EqualTo(udid));
// sid-C pushes us over the cap → sid-A (oldest) evicted.
svc.StoreUdidForSessionId("sid-C", udid);
Assert.That(svc.GetUdidFromSessionId("sid-A"), Is.Null);
Assert.That(svc.GetUdidFromSessionId("sid-B"), Is.EqualTo(udid));
Assert.That(svc.GetUdidFromSessionId("sid-C"), Is.EqualTo(udid));
}
[Test]
public void Constructor_rejects_non_positive_cap()
{
Assert.Throws<System.ArgumentOutOfRangeException>(() => new ShadowverseSessionService(maxEntries: 0));
Assert.Throws<System.ArgumentOutOfRangeException>(() => new ShadowverseSessionService(maxEntries: -1));
}
}