Session.channelRead releases messages that are not frames of its own
session: anything arriving on a channel that is neither its frontend nor
its backend, and any non-ByteBuf message from either. Neither branch had a
test. SessionFrameOwnershipTest builds a Session directly and checks both,
including that a stray channel does not close the real session; replacing
either release with a no-op makes it fail.
docs/testing.md and docs/operations.md no longer list these as untested.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcCN2hYyqmU22X2Xfg1nwe
A parallel mutation pass over Session and ConnectionGate (17 sites, three
workers, failures re-run to rule out load-induced flakiness) left five
sites unguarded. Three of them were buffer handling that only integration
tests reached, so it moves into small classes with direct tests:
TransitionFrames (mapping buffered start-up frames and appending the
rewritten opening packets) and TransitionBuffer (the bounded queue that
releases what it holds). transferOpening now uses ByteBufs.fill. A
mutation run over the two new classes catches all four of their release
sites.
Two defensive branches in Session stay untested and are listed in
docs/testing.md: a non-ByteBuf message (its input always comes from the
frame decoder) and a message from a channel that is neither side of the
session.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcCN2hYyqmU22X2Xfg1nwe
The start scripts now pass -Dio.netty.leakDetection.level=disabled; it can
be re-enabled per deployment with JAVA_OPTS. In its place every
proxy-core test class runs under PARANOID detection through LeakGate (298
tests, no leaks).
Session also moves off hand-written releases at the sites a mutation pass
found unguarded: the login-wait guard is now a FrameTransformHandler and
buffered transition frames go through one mapTransitionFrame helper that
releases on every failure. ConnectionGate gets unit tests with a
TrackingAllocator covering every exit path.
The second mutation pass over Session was stopped at 11 of 24 sites, so
about ten Session releases (mainly transferOpening and the disconnect
packet) are not verified by mutation; docs/testing.md says so.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcCN2hYyqmU22X2Xfg1nwe
NetworkTransport picks epoll on Linux, kqueue on macOS and NIO
elsewhere, or when the native library cannot load. The player listener,
session event loops, backend dials and the DNS resolver all take their
channel types from the selected transport, so backend channels keep
sharing the player's event loop. A new `transport: AUTO|NIO|EPOLL|KQUEUE`
setting forces one choice; an unavailable native transport fails
startup instead of falling back silently. The event loops move from the
deprecated NioEventLoopGroup to MultiThreadIoEventLoopGroup.
The distribution ships the linux-x86_64/aarch_64 epoll and
osx-x86_64/aarch_64 kqueue natives. The start scripts and tests pass
--enable-native-access=ALL-UNNAMED.
On the loopback session benchmark, epoll beat NIO on every median and
regressed nowhere. Only window-1 p50 latency separated cleanly (0.060 vs
0.070 ms); see benchmarks/results/2026-10-05-network-transport.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcCN2hYyqmU22X2Xfg1nwe
The PLAY relay called writeAndFlush for every decoded frame. With
TCP_NODELAY, a socket read holding many small packets (a typical server
tick) cost one flush, write syscall and TCP segment per frame. Write each
frame and flush the peer on channelReadComplete. A per-batch event-loop
task flushes frames forwarded outside a socket read, such as delayed tab
completion. Paused-frame replay keeps writeAndFlush.
ProxySessionBenchmark gains --burst plus proxy I/O CPU and TCP segment
counters. On a 4 vCPU Linux container with 32 x 33-byte frames per write,
TCP segments/frame fell from 1.379 to 0.117 and proxy I/O CPU/frame from
4079 to 1827 ns (medians of six paired phases). The existing 1 KiB window
1 and 16 workloads showed no regression.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcCN2hYyqmU22X2Xfg1nwe