Bound aiosendspin_server.close() timeout to stop harness SIGKILLs - #96
Merged
Conversation
aiosendspin's SendspinServer.close() can hang indefinitely for client-initiated connections: it awaits an unbounded asyncio.gather(*disconnect_tasks) where each disconnect() cancels the connection's message-loop task with no timeout on the cancellation await (Sendspin/aiosendspin#299). By the time close() runs in the adapter's finally block, the scenario summary is already written and the run has already succeeded or hit its error path, so a hang here is pure post-result cleanup that shouldn't fail an otherwise-passing case. Wrap it in a 5s asyncio.wait_for so the adapter process exits on its own instead of requiring the harness's 30s SIGKILL. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This was referenced Sep 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
client-initiated-pcm(aiosendspin server, sendspin-jvm client) fails in CI, but both sides actually succeed at the protocol level — the client exits 0 and audio hashes match in both summaries. The failure is caused by the aiosendspin server process hanging during shutdown after the result is already recorded, and getting SIGKILL'd by the harness's 30s timeout. Becausestr(asyncio.TimeoutError())is"", the runner reports this as a failure with an empty reason.Root cause is a real defect in aiosendspin, filed separately: Sendspin/aiosendspin#299.
SendspinServer.close()runs an unboundedasyncio.gather(*disconnect_tasks, ...)for_clients-tracked (client-initiated) connections, where each task callsSendspinConnection.disconnect(), which cancelsself._message_loop_taskand awaits it with no timeout. Two of the three cleanup phases inclose()already useasyncio.timeout(1.0); this one doesn't.This PR is a harness-side workaround, not a fix for the library bug: it wraps the adapter's
await server.close()call in_run()'sfinallyblock withasyncio.wait_for(..., timeout=5.0), logging and continuing on timeout instead of letting it hang. By the timeclose()runs, the scenario summary has already been written (success or error path), so a slow/hungclose()is pure post-result cleanup and shouldn't fail an otherwise-passing case.This surfaced via Sendspin/sendspin-jvm#25's CI run.
Test plan
Verified locally against the exact sibling-repo layout CI uses (
conformance,aiosendspin,sendspin-cli,sendspin-jvm), built thesendspin-jvm-clientadapter JAR, and ranconformance run --from aiosendspin --to sendspin-jvm:Before the fix:
client-initiated-pcmfails withserver_exit_code: -9(harness SIGKILL after timeout waiting on the server).After the fix:
client-initiated-pcmpasses (PCM hashes match exactly,server_exit_code: 0). The server log showsWARNING:root:server.close() timed out after 5s; continuing shutdown— confirming the underlying aiosendspin hang still occurs, but the adapter now recovers instead of being killed.Confirmed no regression: all previously-passing scenarios (
server-initiated-pcm,server-initiated-metadata,server-initiated-artwork,server-initiated-controller,server-initiated-flac,server-initiated-pcm-24bit) still pass, and theirserver.logfiles show no timeout warning —close()still completes immediately for non-client-initiated connections, so this is a no-op for those paths.python -m compileall srcpasses.Reproduced the failure locally
Verified the fix resolves it without touching other scenarios
Not run against CI yet (this PR)
🤖 Generated with Claude Code