mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-09 16:14:40 +00:00
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