Class WorkerNIO
- Direct Known Subclasses:
Worker
Selector and every socket registered with it: connect, read and write readiness, and the periodic timeout scan that heart-beats and idle-timeouts depend on.There is no queue in this class. The task queue and its callbacks live in the com.iizix.Worker subclass, which is where several of these comments used to point.
How the loop waits depends on what is registered. With a channel registered it blocks in Selector.select, as it always did. With NONE - every client session's worker of the server: its WebSocket is Jetty's and never enters here - it waits on a condition instead, woken by wakeupSelector(). The difference is the carrier: a virtual thread blocked in Selector.select does not unmount (JDK 25, sun.nio.ch.SelectorImpl.doSelect runs under jdk.internal.misc.Blocker.begin, which only COMPENSATES by letting the scheduler add a carrier, up to jdk.virtualThreadScheduler.maxPoolSize, 256 by default). Measured 2026-10-07 (thread dump, CC-FIX-READATOM-RECONNECT O-1): 246 idle session workers held 246 of the 256 carriers, no other virtual thread ran - Jetty's acceptor among them - and the server answered nobody, silently. A condition wait unmounts. A worker that does select on a virtual thread is counted (getPinnedCarrierCount()), and ONE warning names the count when it nears the scheduler's maximum.
- Author:
- Christopher Mindus
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final intThe multi-purpose buffer allocated by the worker, 64 KB.Constructor Summary
ConstructorsMethod Summary
Modifier and TypeMethodDescriptionvoidChecks if the worker is disposed.booleandispose()Disposes of the worker: every registered socket is closed immediately and the selector is closed, which ends theprocess()loop.static intGets the carrier maximum of the virtual-thread scheduler, asreadMaxCarriers()read it.static intGets how many virtual worker threads are blocked in a carrier-pinningSelector.selectright now, over every worker of this JVM.protected longGets the smallest gap allowed between two timeout scans, in milliseconds.booleanChecks if the worker is disposed of.static booleanGets whether a virtual thread blocked inSelector.selectholds its carrier on this JDK and platform, measured once when first asked; seeWorkerNIO.SelectPins.voidprocess()Runs the selector loop until it stops, which is normally when the worker is disposed.protected booleanProcesses once a single write-read-execute operation.protected booleanprocessSelectorKeys(boolean now) Process selector keys.register(ISocketComm comm, SelectableChannel channel, int ops) Registers a SocketComm - opened - instance with the I/O Selector associated with the Worker.booleanregisterTimeoutHandler(IWorkerTimeout handler) Registers a new timeout handler.booleanunregisterTimeoutHandler(IWorkerTimeout handler) Unregisters a timeout handler.protected voidWakes the selector, so a blockedselect()returns and the loop takes another pass - and the condition a worker without a channel waits on instead, which is the same pass.
Field Details
LAN_BUFFER_SIZE
public static final int LAN_BUFFER_SIZEThe multi-purpose buffer allocated by the worker, 64 KB.Chosen to maximize localhost communication and large buffer transmits over 1Gb LAN's. There is ONE of these per worker, shared by every socket it serves, not one per connection - which is why 64 KB is affordable here.
- See Also:
Constructor Details
WorkerNIO
Creates the worker and opens its selector.- Throws:
IOException- If an I/O error occurs.
Method Details
registerTimeoutHandler
Registers a new timeout handler. Calling this method more than once for the same handler does not change anything.- Parameters:
handler- The timeout handler.- Returns:
- true for success, false if already registered.
unregisterTimeoutHandler
Unregisters a timeout handler.Deliberately does NOT wake the selector, and does not need to. The asymmetry with
registerTimeoutHandler(IWorkerTimeout)is the point: adding a handler can only shorten the wait thatselect()is already blocked on, which requires a wake-up to take effect, whereas removing one can only lengthen it. Aselect()still blocked on the removed handler's deadline simply wakes early, finds it gone and recomputes.- Parameters:
handler- The timeout handler.- Returns:
- true for success, false if not registered.
register
public SelectionKey register(ISocketComm comm, SelectableChannel channel, int ops) throws IOException Registers a SocketComm - opened - instance with the I/O Selector associated with the Worker.- Parameters:
comm- The communication instance to attach to the selector.channel- The selectable channel (socket or server-socket channels).ops- The Operations the communication instance wishes to listen to.- Returns:
- The selection key for the socket channel.
- Throws:
IOException- For I/O errors.
processSelectorKeys
Process selector keys.- Parameters:
now- Flag to process now or at a later stage.- Returns:
- true if something was found to process, false for nothing.
- Throws:
IOException- For I/O exceptions.
isSelectPinningCarrier
public static boolean isSelectPinningCarrier()Gets whether a virtual thread blocked inSelector.selectholds its carrier on this JDK and platform, measured once when first asked; seeWorkerNIO.SelectPins. Where it does not,getPinnedCarrierCount()stays zero.- Returns:
- true if it does.
getPinnedCarrierCount
public static int getPinnedCarrierCount()Gets how many virtual worker threads are blocked in a carrier-pinningSelector.selectright now, over every worker of this JVM. Zero for a server whose sessions have no channel registered with their workers (the WebSocket is Jetty's); one per terminal host session.- Returns:
- The count.
getMaxCarriers
public static int getMaxCarriers()Gets the carrier maximum of the virtual-thread scheduler, asreadMaxCarriers()read it.- Returns:
- The maximum.
getTimeoutScanMinIntervalMS
protected long getTimeoutScanMinIntervalMS()Gets the smallest gap allowed between two timeout scans, in milliseconds.Override to tune. See
TIMEOUT_SCAN_MIN_INTERVAL_MSfor what the value trades.- Returns:
- The minimum interval in milliseconds.
process
public void process()Runs the selector loop until it stops, which is normally when the worker is disposed.Blocks the calling thread for the life of the worker. Callers own the thread and the decision about what an escaping throwable means:
WorkerThread.run()andServerShellboth catchThrowable, log SEVERE and tear their session down.processOnce
protected boolean processOnce()Processes once a single write-read-execute operation. This is called from the process-loop and postModalWait.- Returns:
- true for success, false for cancelled or error.
checkDisposed
public void checkDisposed()Checks if the worker is disposed. This method does nothing if the worker is not disposed of.- Throws:
IllegalStateException- If the worker is disposed of.
dispose
public boolean dispose()Disposes of the worker: every registered socket is closed immediately and the selector is closed, which ends theprocess()loop.There is no task queue at this level, so nothing is "cancelled" here. The queue and its callback cancellation belong to
com.iizix.Worker.dispose(), which overrides this.- Returns:
- true if disposed of, false if already disposed.
isDisposed
public boolean isDisposed()Checks if the worker is disposed of.- Returns:
- true if worker is disposed, false otherwise.
wakeupSelector
protected void wakeupSelector()Wakes the selector, so a blockedselect()returns and the loop takes another pass - and the condition a worker without a channel waits on instead, which is the same pass.Called from this class, from subclasses, and from
SocketCommNIOin the same package - it is not subclass-only, whatever the previous comment here said.