Files
amethyst/quic
Claude c549bd22df perf: isolate QUIC blocking socket I/O onto dedicated threads
Part A of the dispatchers/thread-caps audit — the one genuine at-scale
starvation the audit found.

UdpSocket.receive() does a blocking DatagramChannel recvfrom that parks its
thread for the ENTIRE life of the connection. It ran via
withContext(Dispatchers.IO) from a read loop already on Dispatchers.IO, so
the blocking call pinned one shared IO-pool thread per connection. Past ~64
concurrent connections that starves ALL other Dispatchers.IO work in the
process — this module's and the host app's alike.

Give each socket two dedicated daemon threads: recvDispatcher for the
perpetually-parked receive and sendDispatcher for the send (they can't share
one thread — the receive would monopolise it). QUIC's blocking socket I/O now
never touches the shared pool. connect() keeps its one-shot DNS/bind on
Dispatchers.IO (setup cost, not a lifetime parker).

close() calls shutdownNow() on both executors: interrupting the recv worker
breaks the parked recvfrom immediately (ClosedByInterruptException, caught as
ClosedChannelException -> receive() returns null), so the threads exit
promptly instead of leaking per closed connection. The closed-check is hoisted
out of withContext so a post-close call fails fast without dispatching onto a
shut-down executor.

Verified: QuicConnectionDriverLifecycleTest (100 session open/close cycles,
asserts thread growth <=16 and no FD leak) passes, confirming the two new
threads per socket are reclaimed on teardown. New UdpSocketTest covers
round-trip, the dedicated-thread isolation, thread shutdown on close, and the
after-close contract. Full :quic:jvmTest suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ANuUziXKRafSTBxbh4SMoq
2026-07-02 21:35:57 +00:00
..