mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
test: pin Blossom notes across the consume window - LargeSoftCache evicts
Root cause of the recurring DesktopBlossomServerListTest CI failures, finally caught by the diagnostic added last round (flow=[], getter=null, no verification warning): DesktopLocalCache.addressableNotes holds notes via SoftReference (LargeSoftCache). Under CI memory pressure a GC evicts the consumed note between cache.consume() and the state's getOrCreateAddressableNote(), which then mints a fresh EMPTY note - the flow can never surface the servers. Production is immune because BlossomServerListState pins blossomListNote as a field for its lifetime; the tests just never held a strong reference across that window. All three tests now pin the note before consuming, and the state test asserts the event landed before construction so an eviction fails fast at the source. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011S1vbFWVVAMFDT8PTgdibV
This commit is contained in:
+17
-2
@@ -63,9 +63,14 @@ class DesktopBlossomServerListTest {
|
||||
val servers = listOf("https://blossom.example.com", "https://cdn.example.org")
|
||||
val event = signedServerList(servers, signer)
|
||||
|
||||
// Pin the note across consume -> read: the cache holds notes via
|
||||
// SoftReference (LargeSoftCache), so without a strong reference a GC
|
||||
// between the two calls evicts the note and the re-fetch mints an
|
||||
// empty one (seen on memory-tight CI runners).
|
||||
val pinned = cache.getOrCreateAddressableNote(event.address())
|
||||
cache.consume(event, relayUrl)
|
||||
|
||||
val stored = cache.getOrCreateAddressableNote(event.address()).event as? BlossomServersEvent
|
||||
val stored = pinned.event as? BlossomServersEvent
|
||||
assertNotNull(stored, "kind 10063 event must be stored in the addressable cache")
|
||||
assertEquals(servers, stored.servers())
|
||||
}
|
||||
@@ -78,10 +83,11 @@ class DesktopBlossomServerListTest {
|
||||
val newer = signedServerList(listOf("https://new.example.com"), signer, createdAt = 2_000)
|
||||
val older = signedServerList(listOf("https://old.example.com"), signer, createdAt = 1_000)
|
||||
|
||||
val pinned = cache.getOrCreateAddressableNote(newer.address())
|
||||
cache.consume(newer, relayUrl)
|
||||
cache.consume(older, relayUrl)
|
||||
|
||||
val stored = cache.getOrCreateAddressableNote(newer.address()).event as? BlossomServersEvent
|
||||
val stored = pinned.event as? BlossomServersEvent
|
||||
assertEquals(listOf("https://new.example.com"), stored?.servers())
|
||||
}
|
||||
|
||||
@@ -98,7 +104,16 @@ class DesktopBlossomServerListTest {
|
||||
val signer = NostrSignerInternal(KeyPair())
|
||||
val servers = listOf("https://blossom.example.com")
|
||||
val event = signedServerList(servers, signer)
|
||||
// Pin the note across the consume -> construct window: the cache holds
|
||||
// notes via SoftReference (LargeSoftCache), and a GC under CI memory
|
||||
// pressure evicted the consumed note before the state's
|
||||
// getOrCreateAddressableNote re-fetched it, minting a fresh EMPTY note
|
||||
// (observed as flow=[] / getter=null on the macOS runner). Production
|
||||
// is immune the same way: BlossomServerListState pins blossomListNote
|
||||
// as a field for its lifetime.
|
||||
val pinned = cache.getOrCreateAddressableNote(event.address())
|
||||
cache.consume(event, relayUrl)
|
||||
assertNotNull(pinned.event, "consume must store the event before the state is built")
|
||||
|
||||
// Unconfined on purpose: the state's stateIn(Eagerly) collector then starts
|
||||
// synchronously and resumes directly on the flowOn(IO) producer thread, so the
|
||||
|
||||
Reference in New Issue
Block a user