fix(geode): make multi-relay compliance test thread-safe

multiRelayPoolReturnsContentFromEachRelay flaked with
"expected:<from-b> but was:<null>": the SubscriptionListener wrote the
per-relay results into a plain HashMap/HashSet, but each relay delivers
its EVENT/EOSE on its own InProcessWebSocket scope (Dispatchers.Default)
and PoolRequests dispatches the listener callbacks outside any lock. Two
relays therefore call `received[relay] = ...` concurrently, and a
HashMap.put racing a rehash can drop an entry, leaving a relay's value
null and failing the assertion.

Use ConcurrentHashMap and ConcurrentHashMap.newKeySet() for the shared
collections. Reproduced within 7 runs before the fix; 80 stress runs
clean after.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeAjLDNBvGPjb5bfViU3ad
This commit is contained in:
Claude
2026-07-25 02:42:36 +00:00
parent 122321cd94
commit d32da653d3
@@ -39,6 +39,7 @@ import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import kotlinx.coroutines.withTimeoutOrNull
import java.util.concurrent.ConcurrentHashMap
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
@@ -518,8 +519,13 @@ class Nip01ComplianceTest : RelayClientTest() {
hub.getOrCreate(relayA).preload(fakeEvent(1, kind = 1, content = "from-a"))
hub.getOrCreate(relayB).preload(fakeEvent(2, kind = 1, content = "from-b"))
val received = mutableMapOf<NormalizedRelayUrl, String>()
val eosed = mutableSetOf<NormalizedRelayUrl>()
// Each relay delivers its EVENT/EOSE on its own InProcessWebSocket
// scope, so these callbacks fire concurrently from two threads. The
// shared collections MUST be thread-safe — a plain HashMap loses a
// write when both threads put during a rehash, leaving a relay's
// entry null and flaking the assertions below.
val received = ConcurrentHashMap<NormalizedRelayUrl, String>()
val eosed = ConcurrentHashMap.newKeySet<NormalizedRelayUrl>()
val ch = Channel<Unit>(UNLIMITED)
client.subscribe(
"multi-1",