Class WorkerNIO

java.lang.Object
com.iizix.nio.WorkerNIO
Direct Known Subclasses:
Worker

public class WorkerNIO extends Object
The Worker drives one 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

    Fields
    Modifier and Type
    Field
    Description
    static final int
    The multi-purpose buffer allocated by the worker, 64 KB.
  • Constructor Summary

    Constructors
    Constructor
    Description
    Creates the worker and opens its selector.
  • Method Summary

    Modifier and Type
    Method
    Description
    void
    Checks if the worker is disposed.
    boolean
    Disposes of the worker: every registered socket is closed immediately and the selector is closed, which ends the process() loop.
    static int
    Gets the carrier maximum of the virtual-thread scheduler, as readMaxCarriers() read it.
    static int
    Gets how many virtual worker threads are blocked in a carrier-pinning Selector.select right now, over every worker of this JVM.
    protected long
    Gets the smallest gap allowed between two timeout scans, in milliseconds.
    boolean
    Checks if the worker is disposed of.
    static boolean
    Gets whether a virtual thread blocked in Selector.select holds its carrier on this JDK and platform, measured once when first asked; see WorkerNIO.SelectPins.
    void
    Runs the selector loop until it stops, which is normally when the worker is disposed.
    protected boolean
    Processes once a single write-read-execute operation.
    protected boolean
    processSelectorKeys(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.
    boolean
    Registers a new timeout handler.
    boolean
    Unregisters a timeout handler.
    protected void
    Wakes the selector, so a blocked select() 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_SIZE
      The 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

      public WorkerNIO() throws IOException
      Creates the worker and opens its selector.
      Throws:
      IOException - If an I/O error occurs.
  • Method Details

    • registerTimeoutHandler

      public boolean registerTimeoutHandler(IWorkerTimeout handler)
      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

      public boolean unregisterTimeoutHandler(IWorkerTimeout handler)
      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 that select() is already blocked on, which requires a wake-up to take effect, whereas removing one can only lengthen it. A select() 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

      protected boolean processSelectorKeys(boolean now) throws IOException
      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 in Selector.select holds its carrier on this JDK and platform, measured once when first asked; see WorkerNIO.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-pinning Selector.select right 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, as readMaxCarriers() 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_MS for 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() and ServerShell both catch Throwable, 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 the process() 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 blocked select() 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 SocketCommNIO in the same package - it is not subclass-only, whatever the previous comment here said.