Class InFlightRequest

java.lang.Object
org.mockserver.netty.InFlightRequest
All Implemented Interfaces:
io.netty.channel.ChannelFutureListener, io.netty.util.concurrent.GenericFutureListener<io.netty.channel.ChannelFuture>, EventListener

public final class InFlightRequest extends Object implements io.netty.channel.ChannelFutureListener
Per-request in-flight token used by the WS7.2 graceful-shutdown connection drain.

A token is created and LifeCycle.requestProcessingStarted() is incremented exactly once when a data-plane HTTP request begins processing in HttpRequestHandler.channelRead0(io.netty.channel.ChannelHandlerContext, org.mockserver.model.HttpRequest). The matching LifeCycle.requestProcessingComplete() decrement is driven by complete(), which is invoked from whichever of these fires first:

  • the response funnel — NettyResponseWriter.sendResponse(...), through which most data-plane responses flow (normal, streaming, chunked, forward/proxy, error/exception, breakpoint-modified); or
  • the end of a response an action handler writes straight to the channel (SSE, WebSocket, gRPC stream, raw-bytes error), which that handler reports through the callback NettyResponseWriter.respondingDirectly() returns, so another exchange's end on the same connection cannot release it; or
  • the channel closeFuture safety net — covers requests that never produce a response (connection drop or pipeline-killing exception mid-processing).

A single compare-and-set guard guarantees the decrement fires exactly once regardless of how many of those hooks fire, so the in-flight counter can never leak (which would make stop() always wait the full drain timeout) nor be decremented twice.

The token is its own ChannelFutureListener (rather than capturing a separate lambda per request), so the only per-request allocation for the safety net is this object itself.

Listener lifecycle. The closeFuture safety net is registered via trackConnectionClose(Channel). On an HTTP/1.1 keep-alive connection the handler sits on the connection channel, whose closeFuture completes only when the whole connection closes — potentially after thousands of requests. A listener added to a future is retained by that future until it completes, so if the safety-net listener were never removed, every completed request on a long-lived connection would leave its listener (the token) pinned for the life of the connection — an unbounded, per-request heap leak. To prevent that, this token remembers the closeFuture it registered on and, the first time complete() wins the CAS, removes itself from that future. After that only a genuine no-response close (connection drop / pipeline-killing exception) still relies on the listener firing, and in that case the listener has already run. Under HTTP/2 the handler sits on a per-stream child channel whose closeFuture completes per request, so the listener would be freed naturally there anyway; removing it on completion is correct on both paths and simply frees it sooner on HTTP/1.1.

Author:
jamesdbloom
  • Field Summary

    Fields inherited from interface io.netty.channel.ChannelFutureListener

    CLOSE, CLOSE_ON_FAILURE, FIRE_EXCEPTION_ON_FAILURE
  • Method Summary

    Modifier and Type
    Method
    Description
    void
    Decrement the in-flight counter, but only the first time this is called for this token.
    void
    operationComplete(io.netty.channel.ChannelFuture future)
     
    Increment the in-flight counter and return a token whose complete() will decrement it exactly once.
    void
    trackConnectionClose(io.netty.channel.Channel channel)
    Register the channel closeFuture safety net for this token: if the channel closes before a response is produced, complete() still fires so the drain counter decrements.

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Method Details

    • started

      public static InFlightRequest started(LifeCycle server)
      Increment the in-flight counter and return a token whose complete() will decrement it exactly once. Returns null when no LifeCycle is available (so callers can no-op safely).
    • trackConnectionClose

      public void trackConnectionClose(io.netty.channel.Channel channel)
      Register the channel closeFuture safety net for this token: if the channel closes before a response is produced, complete() still fires so the drain counter decrements. The future is remembered so complete() can remove this listener on the normal response path, keeping it from accumulating on a long-lived keep-alive connection's closeFuture.

      Called once, on the event-loop thread, immediately after started(LifeCycle) and before any response processing, so the future reference is visible to the later complete() regardless of which thread completes the token.

    • operationComplete

      public void operationComplete(io.netty.channel.ChannelFuture future)
      Specified by:
      operationComplete in interface io.netty.util.concurrent.GenericFutureListener<io.netty.channel.ChannelFuture>
    • complete

      public void complete()
      Decrement the in-flight counter, but only the first time this is called for this token. Safe to call from any thread and from multiple completion hooks.

      On the winning call, also removes this token from the closeFuture it registered on in trackConnectionClose(Channel) so it is not retained by a long-lived connection's closeFuture. removeListener is a no-op when the future has already completed (the channel closed first, so the listener has already been notified and cleared) and when called from within that listener's own notification, so this is safe on every completion path.