2017-05-24 2 views
1

Wir sind gerade dabei, Netty 3.x auf Netty 4.1 in unserem MQTT-basierten Messaging-Backend zu aktualisieren. In unserer Anwendung verwenden wir einen benutzerdefinierten MQTT-Nachrichtendecoder und -Encoder.Korrekte Freigabe von referenzierten ByteBuf-Objekten in Netty 4.1

Für unsere Decoder, verwende ich ByteToMessageDecoder eine zur Zeit wie folgt:

Objekt
public class MqttMessageDecoder extends ByteToMessageDecoder { 

    @Override 
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { 
     if (in.readableBytes() < 2) { 
      return; 
     } 

     ..... 
     ..... 
     ..... 

     byte[] data = new byte[msglength]; 
     in.resetReaderIndex(); 
     in.readBytes(data); 
     MessageInputStream mis = new MessageInputStream(
       new ByteArrayInputStream(data)); 
     Message msg = mis.readMessage(); 
     out.add(msg); 
     ReferenceCountUtil.release(in); 
    } 
} 

wo Message ist unser Brauch, dass auf die nächste ChannelHandler ‚s channelRead() geben wird. Wie Sie sehen können, bin ich mit dem eingehenden ByteBuf Objekt in fertig, sobald ich ein Message Objekt daraus erstelle. Also, da ByteBuf in Netto gezählt wird, ist es richtig, dass ich das in Objekt hier durch Aufruf ReferenceCountUtil.release(in) freigeben muss? Im Idealfall scheint dies nach der doc richtig. Allerdings, wenn ich dies tun, scheine ich die Ausnahme gegenüber zu:

Wed May 24 io.netty.channel.DefaultChannelPipeline:? WARN netty-workers-7 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.channel.ChannelPipelineException: com.bsb.hike.mqtt.MqttMessageDecoder.handlerRemoved() has thrown an exception. 
    at io.netty.channel.DefaultChannelPipeline.callHandlerRemoved0(DefaultChannelPipeline.java:631) [netty-all-4.1.0.Final.jar:4.1.0.Final] 
    at io.netty.channel.DefaultChannelPipeline.destroyDown(DefaultChannelPipeline.java:867) [netty-all-4.1.0.Final.jar:4.1.0.Final] 
    at io.netty.channel.DefaultChannelPipeline.access$300(DefaultChannelPipeline.java:45) [netty-all-4.1.0.Final.jar:4.1.0.Final] 
    at io.netty.channel.DefaultChannelPipeline$9.run(DefaultChannelPipeline.java:874) [netty-all-4.1.0.Final.jar:4.1.0.Final] 
    at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:339) [netty-all-4.1.0.Final.jar:4.1.0.Final] 
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:374) [netty-all-4.1.0.Final.jar:4.1.0.Final] 
    at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:742) [netty-all-4.1.0.Final.jar:4.1.0.Final] 
    at java.lang.Thread.run(Thread.java:745) [na:1.8.0_72-internal] 
Caused by: io.netty.util.IllegalReferenceCountException: refCnt: 0, decrement: 1 
    at io.netty.buffer.AbstractReferenceCountedByteBuf.release(AbstractReferenceCountedByteBuf.java:111) ~[netty-all-4.1.0.Final.jar:4.1.0.Final] 
    at io.netty.handler.codec.ByteToMessageDecoder.handlerRemoved(ByteToMessageDecoder.java:217) ~[netty-all-4.1.0.Final.jar:4.1.0.Final] 
    at io.netty.channel.DefaultChannelPipeline.callHandlerRemoved0(DefaultChannelPipeline.java:626) [netty-all-4.1.0.Final.jar:4.1.0.Final] 
    ... 7 common frames omitted 

Das sagt mir, dass, wenn das Kind Kanal geschlossen wird, werden alle Handler in der Pipeline nacheinander entfernt werden. Wenn dieser Decoder-Handler geschlossen ist, geben wir den ByteBuf, der an diesen Decoder angeschlossen ist, explizit frei, was zu der Ausnahme IllegalReferenceCountException führt, wenn die folgende Methode aufgerufen wird.

Dies ist AbstractReferenceCountedByteBuf#release:

@Override 
    public boolean release() { 
     for (;;) { 
      int refCnt = this.refCnt; 
      if (refCnt == 0) { 
       throw new IllegalReferenceCountException(0, -1); 
      } 

      if (refCntUpdater.compareAndSet(this, refCnt, refCnt - 1)) { 
       if (refCnt == 1) { 
        deallocate(); 
        return true; 
       } 
       return false; 
      } 
     } 
    } 

Was ist der richtige Weg ist dann die ByteBuf Objekte freizugeben, nicht um dieses Problem zu begegnen?

Ich bin mit dem PooledByteBufAllocator -

new ServerBootstrap().childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) 

Bitte lassen Sie mich wissen, wenn Sie weitere Informationen zur Konfiguration benötigen.


EDIT:

Als Add-on zu Ferrybig Antwort, die ByteToMessageDecoder#channelRead nimmt die Freigabe des eingehenden ByteBuf s von selbst aus. Siehe die finally blockieren -

@Override 
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 
     if (msg instanceof ByteBuf) { 
      CodecOutputList out = CodecOutputList.newInstance(); 
      try { 
       ByteBuf data = (ByteBuf) msg; 
       first = cumulation == null; 
       if (first) { 
        cumulation = data; 
       } else { 
        cumulation = cumulator.cumulate(ctx.alloc(), cumulation, data); 
       } 
       callDecode(ctx, cumulation, out); 
      } catch (DecoderException e) { 
       throw e; 
      } catch (Throwable t) { 
       throw new DecoderException(t); 
      } finally { 
       if (cumulation != null && !cumulation.isReadable()) { 
        numReads = 0; 
        cumulation.release(); 
        cumulation = null; 
       } else if (++ numReads >= discardAfterReads) { 
        // We did enough reads already try to discard some bytes so we not risk to see a OOME. 
        // See https://github.com/netty/netty/issues/4275 
        numReads = 0; 
        discardSomeReadBytes(); 
       } 

       int size = out.size(); 
       decodeWasNull = !out.insertSinceRecycled(); 
       fireChannelRead(ctx, out, size); 
       out.recycle(); 
      } 
     } else { 
      ctx.fireChannelRead(msg); 
     } 
    } 

Wenn die eingehenden ByteBuf unten in der Pipeline auf die nächsten Kanal-Handler übertragen wird, der Referenzzähler dieser ByteBuf durch ByteBuf#retain erhöht und so, wenn der nächsten Handler nach dem Decoder ist Ihr Unternehmen Handler (was normalerweise der Fall ist), müssen Sie das Objekt ByteBuf dort freigeben, um Speicherlecks zu vermeiden. Dies wird auch in der docs hier erwähnt.

Antwort

1

Nicht alle Handler erfordern, dass das übergebene Bytebuf zerstört wird. ByteToMessageDecoder ist einer von ihnen.

Der Grund dafür ist, dass diese Prozedur mehrere eingehende bytebufs sammelt, und setzt sie für Ihre Anwendung als 1 kontinuierlichen Strom von Bytes für die einfache Codierung, und brauchen nicht, diese Stücke zu handhaben, sich

Denken Sie daran, dass Sie müssen jedes bytebufs, das Sie erstellen, manuell freigeben, indem Sie entweder oder readSlice verwenden, wie von der Javadoc angegeben.