Repository navigation
Dispose upstream connection when a cancelled exchange discards the response in NettyRoutingFilter - #4283
Conversation
|
@ryanjbaxter @spencergibb |
|
@ryanjbaxter @spencergibb How to occur
If the cancel arrives before After that nothing releases the body. It stays in In our productionOur gateway serves mobile clients, so cancelled requests are common. We added a small guard filter of our own that records the same two moments, and it has logged exactly this state. The response headers had arrived, but |
ryanjbaxter
left a comment
There was a problem hiding this comment.
LGTM. Can you submit this against the 5.0.x branch?
An exchange cancelled between the upstream response arriving and NettyRoutingFilter storing the connection leaks the buffered body, since nothing subscribes to the response and NettyWriteResponseFilter's cleanup has already run with no connection to dispose. Signed-off-by: raccoonback <kosb15@naver.com>
45c5c25 to
5f7cf71
Compare
|
@ryanjbaxter |
|
@ryanjbaxter |
|
Thanks! We will merge it as soon as our release is done |
Problem
NettyRoutingFilterputs the upstream connection intoCLIENT_RESPONSE_CONN_ATTRin theresponseConnection(...)callback. The exchange can be cancelled after the response arrives but before that line runs. Then the response is discarded and nobody subscribes to it.Two things happen in that case:
NettyWriteResponseFilteralready ran ondoFinally(CANCEL).CLIENT_RESPONSE_CONN_ATTRwas still null, so it disposed nothing.connection.inbound().receive(). The upstream body stays in theFluxReceivequeue.After that, nothing releases the buffer. The two places that could have released it do not:
FluxReceiveclears the queue only incleanQueue(), and every call to it is behindisCancelled(). There is no path forinboundDone.HttpClientOperations.onInboundClose()overridesChannelOperationsand does not calldiscardWhenNoReceiver(). So closing the channel does not help either.Our gateway is used by mobile clients. They cancel requests often, so we get many HTTP 499. We see this leak in production every day. The number is small, but it does not stop.
In every report the last access is the enqueue, and there is no poll after it. So the body was never read.
Solution
Add a discard handler, so the connection is disposed when the response is discarded.
Why dispose the connection.
After the value is discarded, the connection is the only handle left to the buffered body. Disposing it cancels the inbound receiver, and cancellation is what reaches
cleanQueue().This is the same thing
NettyWriteResponseFilterdoes in its cleanup, only from a point where the connection is still reachable.It does not replace the existing cleanup.
Once the response reaches the filter chain,
CLIENT_RESPONSE_CONN_ATTRis set andNettyWriteResponseFilterdisposes the connection.FluxTimeout, for example, drops the value withonNextDropped. That is a different hook, so the handler does not run there, but the cleanup covers it.