Skip to content

Close kqueue sockets before CxPlatSocketDelete returns - #6421

Open
Joel Kiptoo (Kiptoo-Deus) wants to merge 1 commit into
microsoft:mainfrom
Kiptoo-Deus:kqueue-close-socket-on-delete
Open

Joel Kiptoo (Kiptoo-Deus) wants to merge 1 commit into
microsoft:mainfrom
Kiptoo-Deus:kqueue-close-socket-on-delete

Conversation

@Kiptoo-Deus

Copy link
Copy Markdown

Description

Fixes the macOS part (cause 1) of #6374. The binding that accepted connections hold on to (cause 2) is not changed here.

On the kqueue datapath, CxPlatSocketContextUninitialize removes the read filter and queues ShutdownSqe, and the close() happens later in CxPlatSocketContextUninitializeComplete on the event queue worker. So CxPlatSocketDelete returns while the port is still bound, and binding the same port straight away (for example, starting a new listener after ListenerClose) can fail with QUIC_STATUS_ADDRESS_IN_USE.

This closes the socket in CxPlatSocketContextUninitialize, right after the read filter is removed, and sets SocketFd to INVALID_SOCKET so CxPlatSocketContextUninitializeComplete skips its close. This is safe because:

  • CxPlatRundownReleaseAndWait(&UpcallRundown) has already completed, so any event the worker has dequeued for this socket fails CxPlatRundownAcquire in CxPlatSocketContextIoEventComplete and doesn't touch the fd.
  • Closing the fd also removes any EVFILT_WRITE filter registered for pending sends.
  • The rest of the cleanup (receive block, pending sends, rundown, processor context, binding release) still runs on the worker after ShutdownSqe.

This follows the patch proposed in the issue by William Smith (@WilliamSmithEdward), who is credited as co-author.

The epoll datapath closes its sockets the same deferred way, but with SO_REUSEPORT on multi-partition server sockets it doesn't show up, and I can't test Linux changes here, so it is left as is.

Testing

Added DataPathTest.UdpRebindSamePortAfterDelete (Windows and macOS only, since epoll still defers the close): bind a UDP socket on the unspecified address, delete it, and bind the same port again 100 times.

On macOS arm64 (Release, quictls):

  • Without the fix the IPv6 (dual-mode) case fails in 20 out of 20 runs, at the rebind. IPv4 happens to pass.
  • With the fix both cases pass in 20 out of 20 runs.
  • msquicplatformtest --gtest_filter=DataPath*: everything passes except XdpMapMode_ZeroConfigUsesNormalPath and XdpMapMode_InitFailsWithoutRawDatapath, which fail on macOS without this change too.
  • msquictest --gtest_filter=*Listener*:*Basic*:*Handshake*:*Rebind*:*Shutdown*: 1127 of 1128 passed. The one failure was a single Misc/WithAbortiveArgs.AbortiveShutdown case, which fails intermittently (a different case each time) on main without this change as well.

Documentation

None.

On macOS, CxPlatSocketContextUninitialize only queued the close of the
socket to the event queue worker, so CxPlatSocketDelete returned while
the port was still bound. Binding the same port right after deleting a
listener's socket could then fail with QUIC_STATUS_ADDRESS_IN_USE.

Close the socket right after removing its read filter. Events the worker
has already dequeued are dropped by the upcall rundown, and the rest of
the cleanup stays on the event queue.

Fixes part 1 of microsoft#6374

Co-authored-by: WilliamSmithEdward <116382973+WilliamSmithEdward@users.noreply.github.com>
@Kiptoo-Deus
Joel Kiptoo (Kiptoo-Deus) requested a review from a team as a code owner October 26, 2026 18:09

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant