Class InFlightRequest
- All Implemented Interfaces:
io.netty.channel.ChannelFutureListener,io.netty.util.concurrent.GenericFutureListener<io.netty.channel.ChannelFuture>,EventListener
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
closeFuturesafety 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 TypeMethodDescriptionvoidcomplete()Decrement the in-flight counter, but only the first time this is called for this token.voidoperationComplete(io.netty.channel.ChannelFuture future) static InFlightRequestIncrement the in-flight counter and return a token whosecomplete()will decrement it exactly once.voidtrackConnectionClose(io.netty.channel.Channel channel) Register the channelcloseFuturesafety net for this token: if the channel closes before a response is produced,complete()still fires so the drain counter decrements.
-
Method Details
-
started
Increment the in-flight counter and return a token whosecomplete()will decrement it exactly once. Returnsnullwhen noLifeCycleis available (so callers can no-op safely). -
trackConnectionClose
public void trackConnectionClose(io.netty.channel.Channel channel) Register the channelcloseFuturesafety 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 socomplete()can remove this listener on the normal response path, keeping it from accumulating on a long-lived keep-alive connection'scloseFuture.Called once, on the event-loop thread, immediately after
started(LifeCycle)and before any response processing, so the future reference is visible to the latercomplete()regardless of which thread completes the token. -
operationComplete
public void operationComplete(io.netty.channel.ChannelFuture future) - Specified by:
operationCompletein interfaceio.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
closeFutureit registered on intrackConnectionClose(Channel)so it is not retained by a long-lived connection'scloseFuture.removeListeneris 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.
-