Skip to content

Commit 7492d5d

Browse files
feat(transport-tcp): replace NIO selector with per-connection virtual-thread blocking I/O (#2612)
* feat(transport-tcp): replace NIO selector with per-connection virtual-thread blocking I/O Each connection runs a blocking SocketChannel.read() loop on its own virtual thread instead of a per-connection NIO Selector. On Java 21 blocking-mode reads/writes park the virtual thread and release the carrier, so the selector (which pins the carrier in select()) and the OP_WRITE + Thread.sleep(1) write busy-wait are removed. A full ring buffer applies backpressure (park-and-retry) instead of toggling OP_READ. Public surface, readLock, RingBuffer, and the AsyncTransportInstance callback contract are unchanged: the existing TcpTransportInstanceTest (31 tests) passes unmodified. Scaling probe (TcpTransportInstanceScalingTest): 200 idle connections use 2 carrier threads with the blocking model vs 201 with the selector model. * fix(transport-tcp): emit close log/audit only on successful close Move the "TCP connection closed" debug line and CLOSE audit event out of the finally block and into the success path of close(). Previously they ran even when socketChannel.close() threw and the method rethrew, so a failed close logged both an ERROR audit event and a misleading CLOSE "Closed" event. The readThread.join() stays in finally so the read loop is always awaited. Also correct an inaccurate comment in the scaling test (the stop-flag holder is a field, not a way to avoid one). * fix(transport-tcp): drop dead write -1 check and tidy audit/test nits - write(): blocking SocketChannel.write() never returns -1 (that signals read EOF), so the `written == -1` branch was dead code. A broken or closed connection already surfaces as IOException/AsynchronousCloseException, both handled below. Remove the check. - constructor: errorMsg already embeds e.getMessage(), so the second ERROR audit event duplicated the first. Emit a single event. - constructor: start the read-loop virtual thread last (after the INFO log and CONNECT audit), so an unchecked throw from logging/audit cannot leak an already-running read thread and the open SocketChannel — the catch only handles IOException and does not stop the read loop. - close(): skip readThread.join() when close() runs on the read thread itself (a disconnect/data listener calling close()), since joining yourself only stalls for the timeout and the loop already exits once open is false. - scaling test: take one Thread.getAllStackTraces() snapshot so carriers and total are counted from the same instant instead of two separate calls. - scaling test: exclude ForkJoinPool.commonPool workers from the carrier count so unrelated parallel-stream workers cannot inflate it.
1 parent 1b96138 commit 7492d5d

2 files changed

Lines changed: 253 additions & 203 deletions

File tree

0 commit comments

Comments
 (0)