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

Most upvoted comments

@rkapsi - I was able to isolate the root cause and I have a fix … stand by for a PR.