Conversation
lhoward
added a commit
to PADL/SwiftOCA
that referenced
this pull request
Sep 11, 2026
Cancelling a FlyingSocks stream or datagram endpoint's run() cancels its socket pool, which stops its event queue under the wait in progress. On Darwin that wait can then fail with EBADF instead of ending as a cancellation: a race, lost in most runs. When it was lost, the endpoint logged a critical "server error ... kqueue kevent" and threw that error. It also skipped removing itself from its device, so it stayed registered. When the task has been cancelled, close the socket, remove the endpoint and throw CancellationError, whatever the pool threw. That covers the FlyingFox we resolve (0.27.1) as well as swhitty/FlyingFox#242, which makes the pool itself throw CancellationError. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Fq7ie1LzJERrh9uRuZZWE
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #242 +/- ##
==========================================
+ Coverage 93.72% 93.74% +0.02%
==========================================
Files 72 72
Lines 3777 3791 +14
==========================================
+ Hits 3540 3554 +14
Misses 237 237 ☔ View full report in Codecov by Harness. |
withPacketInfoControl built the in_pktinfo or in6_pktinfo in the element storage of a ManagedBuffer, which starts out uninitialised, and: - For IPv4 it wrote the local address to ipi_addr and never set ipi_spec_dst, the field ip(7) says sendmsg(2) takes the source address from. So the source was whatever was in memory, and sendmsg could fail with ENETUNREACH, as sendMessage_WithPacketInfo_RoundTripsDatagram does on some Linux builds. - It read the address by reinterpreting the bytes of the any SocketAddress existential. Those bytes are the address only when the value is held inline: a sockaddr_in6 or sockaddr_storage is boxed, so what was read was the box. Zero the structure, read the address from makeStorage(), and put an IPv4 source in ipi_spec_dst. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Fq7ie1LzJERrh9uRuZZWE
Contributor
Author
|
The Linux Swift 6.0 and 6.2 failures here are in |
2 tasks
Cancelling SocketPool.run() while it waits on its event queue made it
throw the error of the interrupted wait instead of CancellationError. On
Darwin, getNotifications()'s cancellation handler stops the queue, which
closes the kqueue under the kevent call in progress. That call fails with
EBADF, and run() threw SocketError.makeFailed("kqueue kevent"). Throw
CancellationError when the wait fails after the task has been cancelled.
HTTPServer.run() logged every error as a critical server error, including
this one, so each cancelled server logged "server error: ...kqueue
kevent...". A cancellation is not a server error; don't log it as one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Fq7ie1LzJERrh9uRuZZWE
lhoward
force-pushed
the
pool-run-cancellation
branch
from
September 11, 2026 04:41
c622d6e to
2414491
Compare
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
Cancelling
SocketPool.run()while the pool waits on its event queue throws the interrupted wait's error instead ofCancellationError, andHTTPServer.run()then logs it as a critical server error. The result is that every cancelledHTTPServeron Darwin logs:Cause
getNotifications()runs the blocking wait on its dispatch queue, and its cancellation handler callsstopQueue()to break that wait. ForkQueue,stop()closes the kqueue whilekeventis still waiting on it. SokeventreturnsEBADF,getNotifications()throwsSocketError.makeFailed("kqueue kevent"), and that becomesrun()'s error.Changes
SocketPool.getNotifications(): if the wait fails after the task has been cancelled, throwCancellationError.HTTPServer.run(): don't log aCancellationErroras a critical server error. Other errors are still logged.On Linux, as far as I can tell from reading the code, the
ePollqueue already comes out asCancellationError:stop()wakes the wait through the canary eventfd, and the nextgetNotifications()call'sTask.checkCancellation()throws. So only the logging change applies there.Reproducing
It's a race, and whether it happens depends on how far the pool has got when it's cancelled. The existing
taskCanBeCancelledcancels as soon as the server is listening, before the pool is waiting on its queue, so it doesn't see this. I cancelled a server 200 ms after it started listening, three times per build:SocketError(kqueue kevent,EBADF)main(bd32d43)Tests
HTTPServerTests.taskCanBeCancelled_WhileWaitingForConnections. It does that 5 times and expectsCancellationErroreach time.SocketPoolchange it failed every time I ran it, with 5 or 6 issues per run, eachexpected error of type CancellationError, but ".failed(type: "kqueue kevent", errno: 9, ...)" of type SocketError was thrown instead.HTTPServerTests.taskCanBeCancellednow expectsCancellationError, not any error.The full suite passes on macOS (Swift 6.3.3). In 5 runs, one hung in
HTTPClientTests.client_sends_request(), which never cancels a server while it runs. Unmodifiedmainhangs the same way, in 1 of 6 runs, there inhandlerError_ResponseIncludesDateHeader(). So that's an existing intermittent hang, not something this change introduces. I haven't run the suite on Linux.We found this in SwiftOCA, whose device endpoints use
SocketPoolthe same wayHTTPServerdoes.🤖 Generated with Claude Code
https://claude.ai/code/session_012Fq7ie1LzJERrh9uRuZZWE