netty: IllegalReferenceCountException with DefaultHttp2ConnectionEncoder#writeData() and PooledSlicedByteBuf
I have a custom Channel class that treats each Http2Stream like a connection and it acts like a proxy to translate between H2 and HTTP/1.1. It looks something like this (very similar to Netty’s own HttpToHttp2ConnectionHandler):
// HttpToHttp2ConnectionHandler
public class HttpToHttp2Handler extends ChannelDuplexHandler {
private static final boolean HACK = true;
private final Http2ConnectionEncoder encoder;
private final Http2Stream stream;
private final ChannelHandlerContext h2callback;
private final Channel h2channel;
public HttpToHttp2Handler(Http2ConnectionEncoder encoder, Http2Stream stream) {
this.encoder = encoder;
this.stream = stream;
Http2RemoteFlowController flowController = encoder.flowController();
this.h2callback = flowController.channelHandlerContext();
this.h2channel = h2callback.channel();
}
@Override
public void flush(ChannelHandlerContext ctx) throws Exception {
h2channel.flush();
}
@Override
public void read(ChannelHandlerContext ctx) throws Exception {
h2channel.read();
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
SimpleChannelPromiseAggregator promiseAggregator =
new SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor());
boolean release = true;
boolean endStream = false;
try {
int streamId = stream.id();
if (msg instanceof HttpMessage) {
HttpMessage message = (HttpMessage)msg;
Http2Headers http2Headers = HttpConversionUtil.toHttp2Headers(message, true);
if (message instanceof FullHttpMessage) {
endStream = !((FullHttpMessage)message).content().isReadable();
}
encoder.writeHeaders(h2callback, streamId, http2Headers, 0, endStream, promiseAggregator.newPromise());
}
if (!endStream && msg instanceof HttpContent) {
Http2Headers trailers = EmptyHttp2Headers.INSTANCE;
if (msg instanceof LastHttpContent) {
LastHttpContent lastContent = (LastHttpContent) msg;
trailers = HttpConversionUtil.toHttp2Headers(lastContent.trailingHeaders(), true);
if (trailers.isEmpty()) {
endStream = true;
}
}
ByteBuf content = ((HttpContent) msg).content();
if (HACK) {
ByteBuf copy = h2callback.alloc().buffer();
copy.writeBytes(content);
content = copy;
// content = Unpooled.copiedBuffer(content); // <- works too
} else {
// We're transferring ownership of the ByteBuf to the encoder
// and we're not longer responsible for releasing it.
release = false;
}
encoder.writeData(h2callback, streamId, content, 0, endStream, promiseAggregator.newPromise());
if (!trailers.isEmpty()) {
encoder.writeHeaders(h2callback, streamId, trailers, 0, true, promiseAggregator.newPromise());
}
}
} catch (Throwable t) {
promiseAggregator.setFailure(t);
} finally {
if (release) {
ReferenceCountUtil.release(msg);
}
promiseAggregator.doneAllocatingPromises();
}
}
}
Please notice the if (HACK) {} condition. Everything works if I make a copy of the HttpContent’s underlying ByteBuf which is a PooledSlicedByteBuf. It starts failing with an IllegalReferenceCountException if I try to passthrough the ByteBuf directly and it continues failing if I explicitly retain() it to rule out erroneous release calls on my end. All it does is to raise additional leak detector errors.
The exact exception I’m getting is this:
io.netty.util.IllegalReferenceCountException: refCnt: 0
at io.netty.buffer.AbstractByteBuf.ensureAccessible(AbstractByteBuf.java:1407) ~[netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.buffer.AbstractByteBuf.checkIndex(AbstractByteBuf.java:1353) ~[netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.buffer.PooledUnsafeDirectByteBuf.nioBuffer(PooledUnsafeDirectByteBuf.java:324) ~[netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.buffer.AbstractUnpooledSlicedByteBuf.nioBuffer(AbstractUnpooledSlicedByteBuf.java:454) ~[netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.buffer.CompositeByteBuf.nioBuffers(CompositeByteBuf.java:1498) ~[netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.buffer.AbstractUnpooledSlicedByteBuf.nioBuffers(AbstractUnpooledSlicedByteBuf.java:460) ~[netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.buffer.AbstractByteBuf.nioBuffers(AbstractByteBuf.java:1203) ~[netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.handler.ssl.SslHandler.wrap(SslHandler.java:685) ~[netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.handler.ssl.SslHandler.wrap(SslHandler.java:522) ~[netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.handler.ssl.SslHandler.flush(SslHandler.java:490) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.AbstractChannelHandlerContext.invokeFlush0(AbstractChannelHandlerContext.java:787) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.AbstractChannelHandlerContext.invokeFlush(AbstractChannelHandlerContext.java:779) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.AbstractChannelHandlerContext.flush(AbstractChannelHandlerContext.java:760) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.ChannelDuplexHandler.flush(ChannelDuplexHandler.java:117) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.AbstractChannelHandlerContext.invokeFlush0(AbstractChannelHandlerContext.java:787) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.AbstractChannelHandlerContext.invokeFlush(AbstractChannelHandlerContext.java:779) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.AbstractChannelHandlerContext.flush(AbstractChannelHandlerContext.java:760) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.handler.codec.http2.Http2ConnectionHandler.flush(Http2ConnectionHandler.java:163) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.handler.codec.http2.Http2ConnectionHandler.channelReadComplete(Http2ConnectionHandler.java:464) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelReadComplete(AbstractChannelHandlerContext.java:409) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelReadComplete(AbstractChannelHandlerContext.java:391) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelReadComplete(AbstractChannelHandlerContext.java:384) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.ChannelInboundHandlerAdapter.channelReadComplete(ChannelInboundHandlerAdapter.java:97) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelReadComplete(AbstractChannelHandlerContext.java:409) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelReadComplete(AbstractChannelHandlerContext.java:391) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelReadComplete(AbstractChannelHandlerContext.java:384) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.handler.timeout.IdleStateHandler.channelReadComplete(IdleStateHandler.java:275) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelReadComplete(AbstractChannelHandlerContext.java:409) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelReadComplete(AbstractChannelHandlerContext.java:391) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelReadComplete(AbstractChannelHandlerContext.java:384) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.handler.ssl.SslHandler.channelReadComplete(SslHandler.java:928) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelReadComplete(AbstractChannelHandlerContext.java:409) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelReadComplete(AbstractChannelHandlerContext.java:391) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelReadComplete(AbstractChannelHandlerContext.java:384) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.ChannelInboundHandlerAdapter.channelReadComplete(ChannelInboundHandlerAdapter.java:97) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelReadComplete(AbstractChannelHandlerContext.java:409) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelReadComplete(AbstractChannelHandlerContext.java:391) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelReadComplete(AbstractChannelHandlerContext.java:384) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.ChannelInboundHandlerAdapter.channelReadComplete(ChannelInboundHandlerAdapter.java:97) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelReadComplete(AbstractChannelHandlerContext.java:409) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelReadComplete(AbstractChannelHandlerContext.java:391) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelReadComplete(AbstractChannelHandlerContext.java:384) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelReadComplete(DefaultChannelPipeline.java:1339) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelReadComplete(AbstractChannelHandlerContext.java:409) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelReadComplete(AbstractChannelHandlerContext.java:391) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.DefaultChannelPipeline.fireChannelReadComplete(DefaultChannelPipeline.java:932) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:134) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:651) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:574) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:488) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:450) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:873) [netty-all-4.1.7.Final-SNAPSHOT.jar:4.1.7.Final-SNAPSHOT]
at java.lang.Thread.run(Thread.java:745) [?:1.8.0_102]
I haven’t been able to reproduce it locally/with clean traffic but it happens pretty quickly in our production environment.
I’m suspecting it’s something in FlowControlledData#write(...) or maybe CoalescingBufferQueue that doesn’t like PooledSlicedByteBufs.
About this issue
- Original URL
- State: closed
- Created 8 years ago
- Comments: 27 (27 by maintainers)
Commits related to this issue
- retained[Slice|Duplicate] buffer reference count bug Motivation: Currently the ByteBuf created as a result of retained[Slice|Duplicate] attempt to maintain its own reference count, and when this refe... — committed to Scottmitch/netty by Scottmitch 8 years ago
- retained[Slice|Duplicate] buffer reference count bug Motivation: Currently the ByteBuf created as a result of retained[Slice|Duplicate] maintains its own reference count, and when this reference coun... — committed to Scottmitch/netty by Scottmitch 8 years ago
- retained[Slice|Duplicate] buffer reference count bug Motivation: Currently the ByteBuf created as a result of retained[Slice|Duplicate] maintains its own reference count, and when this reference coun... — committed to Scottmitch/netty by Scottmitch 8 years ago
- retained[Slice|Duplicate] buffer reference count bug Motivation: Currently the ByteBuf created as a result of retained[Slice|Duplicate] maintains its own reference count, and when this reference coun... — committed to Scottmitch/netty by Scottmitch 8 years ago
- retained[Slice|Duplicate] buffer reference count bug Motivation: Currently the ByteBuf created as a result of retained[Slice|Duplicate] maintains its own reference count, and when this reference coun... — committed to Scottmitch/netty by Scottmitch 8 years ago
- retained[Slice|Duplicate] buffer reference count bug Motivation: Currently the ByteBuf created as a result of retained[Slice|Duplicate] maintains its own reference count, and when this reference coun... — committed to Scottmitch/netty by Scottmitch 8 years ago
- retained[Slice|Duplicate] buffer reference count bug Motivation: Currently the ByteBuf created as a result of retained[Slice|Duplicate] maintains its own reference count, and when this reference coun... — committed to Scottmitch/netty by Scottmitch 8 years ago
- retained[Slice|Duplicate] buffer reference count bug Motivation: Currently the ByteBuf created as a result of retained[Slice|Duplicate] maintains its own reference count, and when this reference coun... — committed to Scottmitch/netty by Scottmitch 8 years ago
- retained[Slice|Duplicate] buffer reference count bug Motivation: Currently the ByteBuf created as a result of retained[Slice|Duplicate] maintains its own reference count, and when this reference coun... — committed to Scottmitch/netty by Scottmitch 8 years ago
- retained[Slice|Duplicate] buffer reference count bug Motivation: Currently the ByteBuf created as a result of retained[Slice|Duplicate] maintains its own reference count, and when this reference coun... — committed to Scottmitch/netty by Scottmitch 8 years ago
- retained[Slice|Duplicate] buffer reference count bug Motivation: Currently the ByteBuf created as a result of retained[Slice|Duplicate] maintains its own reference count, and when this reference coun... — committed to liuzhengyang/netty by Scottmitch 8 years ago
@rkapsi - I was able to isolate the root cause and I have a fix … stand by for a PR.