When an HTTP2 reactor-netty server is using sendObject when returning the response, sometimes, we see this exception on the server:
12:53:50.905 [reactor-http-nio-1] WARN i.n.channel.DefaultChannelPipeline - An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.
io.netty.util.IllegalReferenceCountException: refCnt: 0
at io.netty.buffer.AbstractByteBuf.ensureAccessible(AbstractByteBuf.java:1454)
at io.netty.buffer.AbstractByteBuf.checkIndex(AbstractByteBuf.java:1383)
at io.netty.buffer.PooledByteBuf.internalNioBuffer(PooledByteBuf.java:203)
at io.netty.handler.ssl.SslHandler.wrap(SslHandler.java:1026)
at io.netty.handler.ssl.SslHandler.wrap(SslHandler.java:829)
at io.netty.handler.ssl.SslHandler.wrapAndFlush(SslHandler.java:800)
at io.netty.handler.ssl.SslHandler.flush(SslHandler.java:781)
at io.netty.channel.AbstractChannelHandlerContext.invokeFlush0(AbstractChannelHandlerContext.java:925)
at io.netty.channel.AbstractChannelHandlerContext.invokeFlush(AbstractChannelHandlerContext.java:907)
at io.netty.channel.AbstractChannelHandlerContext.flush(AbstractChannelHandlerContext.java:893)
at io.netty.handler.logging.LoggingHandler.flush(LoggingHandler.java:304)
at io.netty.channel.AbstractChannelHandlerContext.invokeFlush0(AbstractChannelHandlerContext.java:923)
at io.netty.channel.AbstractChannelHandlerContext.invokeFlush(AbstractChannelHandlerContext.java:907)
at io.netty.channel.AbstractChannelHandlerContext.flush(AbstractChannelHandlerContext.java:893)
at io.netty.handler.codec.http2.Http2ConnectionHandler.flush(Http2ConnectionHandler.java:197)
at io.netty.handler.codec.http2.Http2ConnectionHandler.channelReadComplete(Http2ConnectionHandler.java:555)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelReadComplete(AbstractChannelHandlerContext.java:486)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelReadComplete(AbstractChannelHandlerContext.java:463)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelReadComplete(AbstractChannelHandlerContext.java:456)
at reactor.netty.tcp.SslProvider$SslReadHandler.channelReadComplete(SslProvider.java:842)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelReadComplete(AbstractChannelHandlerContext.java:486)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelReadComplete(AbstractChannelHandlerContext.java:463)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelReadComplete(AbstractChannelHandlerContext.java:456)
at io.netty.handler.logging.LoggingHandler.channelReadComplete(LoggingHandler.java:272)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelReadComplete(AbstractChannelHandlerContext.java:484)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelReadComplete(AbstractChannelHandlerContext.java:463)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelReadComplete(AbstractChannelHandlerContext.java:456)
at io.netty.handler.ssl.SslHandler.channelReadComplete0(SslHandler.java:1313)
at io.netty.handler.ssl.SslHandler.channelReadComplete(SslHandler.java:1302)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelReadComplete(AbstractChannelHandlerContext.java:486)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelReadComplete(AbstractChannelHandlerContext.java:463)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelReadComplete(AbstractChannelHandlerContext.java:456)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelReadComplete(DefaultChannelPipeline.java:1415)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelReadComplete(AbstractChannelHandlerContext.java:482)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelReadComplete(AbstractChannelHandlerContext.java:463)
at io.netty.channel.DefaultChannelPipeline.fireChannelReadComplete(DefaultChannelPipeline.java:925)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:171)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:1589)
So, the buffer is released at HttpOperations.java:491, but the buffer has already been written to the channel (either here or here:), but is not yet flushed and still present in the SSLHandler.pendingUnencryptedWrites data structure
Finally, when SSLHandler.flush is called after channelReadComplete, then it gets the IllegalReferenceCountException when accessing to the buffer from the pendingUnencryptedWrites data structure, because the buffer has already been released at HttpOperations.java:491
When an HTTP2 reactor-netty server is using
sendObject
when returning the response, sometimes, we see this exception on the server:Expected Behavior
Actual Behavior
Steps to Reproduce
See attached reproducer.tgz: reproducer.tgz
To reproduce:
Use java 19
./gradlew build
from one console, start the frontend server (using java19): java -DPROTOCOL=H2 -jar frontend-rn-11x/build/libs/frontend-rn-11x-1.0.0.jar
from another console, start gatling (using java19): java -DPROTOCOL=H2 -DDURATION=20 -jar gatling/build/libs/gatling-1.0.0-all.jar application “H2 simulation / JsonGet” JsonGet
You should then see the IllegalReferenceCountException from the frontend console.
Analysis:
sometimes, it seems that we are facing this situation:
In fact, the above stack trace is triggered by this one (the stream is being closed):
So, the buffer is released at HttpOperations.java:491, but the buffer has already been written to the channel (either here or here:), but is not yet flushed and still present in the SSLHandler.pendingUnencryptedWrites data structure
Finally, when SSLHandler.flush is called after channelReadComplete, then it gets the IllegalReferenceCountException when accessing to the buffer from the pendingUnencryptedWrites data structure, because the buffer has already been released at HttpOperations.java:491
Possible Solution
Your Environment