From b8b25060fb2262935bb160f4a0db99e4b2ca3884 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 8 Jul 2026 19:15:11 +0000 Subject: [PATCH] fix: drain buffered event on EOSE in fetchFirst to avoid race MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A relay sends its matching events before its EOSE, so both an event and the relay's completion can sit buffered in their channels at the same time. The select() over the two channels picks a ready clause at random, so it could process the doneChannel completion first, empty `remaining`, and exit the loop while the matching event was still unread — returning null instead of the event. On a relay completion, drain the event channel first and treat any already-buffered event as the result before marking the relay done. --- .../client/accessories/NostrClientFetchFirstExt.kt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/NostrClientFetchFirstExt.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/NostrClientFetchFirstExt.kt index fb1ab9a855..20e686de6a 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/NostrClientFetchFirstExt.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/NostrClientFetchFirstExt.kt @@ -120,7 +120,17 @@ suspend fun INostrClient.fetchFirst( remaining.clear() } doneChannel.onReceive { relay -> - remaining.remove(relay) + // A relay sends its matching events before its EOSE, so an event may + // already be buffered when this completion fires. select() picks a ready + // clause at random, so without this drain we could treat the relay as done + // and exit while its event still sits unread in the channel. + val buffered = eventChannel.tryReceive().getOrNull() + if (buffered != null) { + result = buffered + remaining.clear() + } else { + remaining.remove(relay) + } } } }