fix: FTS catch-up worker must not leak an uncaught exception at shutdown

The worker's writer-mutex fast path can slip past its own scope
cancellation and run one batch against a store that close() already
freed. The resulting closed-connection exception escaped scope.launch
under a SupervisorJob with no handler — harmless in production, but the
kotlinx-coroutines-test global handler attributes it to whatever runTest
starts next (seen on CI as EventSourceServerTest.countUsesSource failing
with UncaughtExceptionsBeforeTest). Catch, log, and stop the pass:
nothing depends on it — the pre-search drain covers search correctness.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NeoCvXnTxsKzqurkmjdC46
This commit is contained in:
Claude
2026-07-03 19:22:43 +00:00
parent 35449c7437
commit 6760ba450c
@@ -29,6 +29,8 @@ import com.vitorpamplona.quartz.nip01Core.relay.server.policies.RelayLimits
import com.vitorpamplona.quartz.nip01Core.relay.server.policies.VerifyPolicy
import com.vitorpamplona.quartz.nip01Core.store.IEventStore
import com.vitorpamplona.quartz.nip77Negentropy.NegentropySettings
import com.vitorpamplona.quartz.utils.Log
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.Channel
@@ -113,7 +115,22 @@ class NostrServer(
// the burst's final batch commit pokes again, and the
// pre-search drain covers correctness regardless.
while (!ingest.hasBacklog()) {
if (store.ftsCatchUp()) break
val done =
try {
store.ftsCatchUp()
} catch (e: Throwable) {
if (e is CancellationException) throw e
// A shutdown can close the store between this
// worker's cancellation and its last batch (the
// mutex fast path skips the cancellation check),
// and an uncaught throw here poisons unrelated
// runTest tests via the global handler. Nothing
// depends on this pass — the pre-search drain
// covers correctness — so log and stop.
Log.w("NostrServer") { "FTS catch-up batch failed: ${e.message}" }
break
}
if (done) break
}
}
}