0

In my c/s project, c/s need to exchange fixed-size message with netty.
I use pooled direct buffer to buffer message.
Code just like below:

public void channelRead(ChannelHandlerContext ctx, Object msg) {
    ByteBuf in = (ByteBuf) msg;
    try {
        ...
    } finally {
        ReferenceCountUtil.release(msg);
    }
    // alloc larger size of memory to increase memory usage
    ByteBuf buffer = PooledByteBufAllocator.DEFAULT.directBuffer(4* 1024 * 1024, 4* 1024 * 1024);
    // mock exchange message
    for (int i = 0; i < 1024 * 1024; i++) {
        buffer.writeInt(i);
    }
    // code 1
    // ctx.write(buffer);
    // ReferenceCountUtil.release(buffer);
    // code 2
    // ctx.write(buffer, ctx.newPromise().addListener(f -> ReferenceCountUtil.release(buffer)));
    // code 3
    // ctx.write(buffer);
}

As I know, I need to release ByteBuf.
Which is the right way to use buffer, code 1, code 2 or code 3?
Intuitively code 2 may be a good choice.
But code 2 occurs error,

i.n.util.concurrent.DefaultPromise - An exception was thrown by XXXServerHandler$$Lambda$73/0x000000080018ac40.operationComplete()
io.netty.util.IllegalReferenceCountException: refCnt: 0, decrement: 1

Thanks.

shanfeng
  • 503
  • 2
  • 14

2 Answers2

0

You need to do the option 3 "code 3" The handler on your channel pipeline is already calling release, when it is done with the bytebuf.

  • Got it, I see `remove` method will help to release ByteBuf, but it is really complicated and a little magic. Could you give more details about this? – shanfeng Oct 10 '22 at 08:33
0

Use a try-finally block for this:

// alloc larger size of memory to increase memory usage
ByteBuf buffer = PooledByteBufAllocator.DEFAULT.directBuffer(4* 1024 * 1024, 4* 1024 * 1024);
try {
    // mock exchange message
    for (int i = 0; i < 1024 * 1024; i++) {
        buffer.writeInt(i);
    }
    ctx.write(buffer.retain());
} finally {
    buffer.release();
}

When the buffer is acquired from the pool, it has a reference count of 1. You then do things with it, like filling, and if any exception happens here, the code goes into the finally part, releasing the buffer.

A special thing happens when calling a function that is going to continue to run after your function is done, like write. Releasing the buffer manually will be to soon, while it is hard to do safely, so we use the retain trick. We call the method retain on the buffer, so the reference count becomes 2, meaning it our call to release in the finally block will decrement it back to 1, and when write is done, it will be converted to 0 and given back to the pool

Ferrybig
  • 18,194
  • 6
  • 57
  • 79
  • Thanks for your answer. Use try-finally is a good choice in most scenarios, but here remains a question. When we call `ctx.write`, the buffer is wrapped into a elements of queue. It will be consumed in the laster by event loop thread. If we release the buffer after write immediately, the buffer may not be sent yet. – shanfeng Oct 17 '22 at 01:15
  • @shanfeng Note the call to `retain()` after the buffer in the write call, this makes it so you need to call release 2 times from that pint in order to release it, one is being done in the finally block, and the other is being done by the write method itself after it is done writing – Ferrybig Oct 17 '22 at 07:58