Netty:记一Bug(ChannelPipeline 被无限添加,导致死机!)

现象

程序一开始挺流畅,越走越卡,最后“死掉”!

原因

粗心大意,以前设计SDK的是单设备类型的,所以就直接添加 Handler 了。


@Override
public ProtocolSelect getProtocolSelect() {

    if (protocolSelect == null) {
        setProtocolSelect((ctx, b) -> ctx.pipeline()
                .addLast(b ? new R2000MonitorFrameDecoder() : new R2000FrameDecoder())
                .addLast(b ? new R2000MonitorFrameEncoder() : new ByteArrayEncoder())
                .addLast(new R2000ProtocolHandler(this.getMessageCallBacks())));
    }

    return protocolSelect;
}

后面SDK框架改为支持多设备类型的了,添加了协议动态切换功能,Handler 是这样添加的了。(Scala)


// #移除R2000协议
def removeR2000Handler(implicit pipeline: ChannelPipeline): Unit = {
  if (pipeline.context("r2000Decoder") != null) {
    pipeline.remove("r2000Decoder")
    pipeline.remove("r2000Encoder")
    pipeline.remove("r2000Protocol")
  }
}

// #添加R100协议
def addR100Handler(implicit pipeline: ChannelPipeline, monitor: Boolean): Unit = {
  if (pipeline.context("r100Decoder") == null) {
    logger.debug("protocol => r100")

    pipeline.addLast("r100Decoder", if (monitor) new R100MonitorFrameDecoder
    else new R100FrameDecoder).addLast("r100Encoder", if (monitor) new R100MonitorFrameEncoder
    else new R100FrameEncoder).addLast("r100Protocol", new R100ProtocolHandler(DRIVE.getMessageCallBacks))
  }
}

多设备情况下运行是OK的,因为它回判断Handler 是否存在,也带有名字,相同可被覆盖。但是单设备时,还是上面第一种写法,就会出问题了。因为架构设计是多协议的了,所以每次来消息时都会把协议再次添加一遍!!!

最后就这情况
在这里插入图片描述

修复

@Override
   public ProtocolSelect getProtocolSelect() {

       if (protocolSelect == null) {
           setProtocolSelect((pipeline, b) -> {
               if (pipeline.context("r2000Decoder") == null) {
                   pipeline.addLast("r2000Decoder", b ? new R2000MonitorFrameDecoder() : new R2000FrameDecoder())
                           .addLast("r2000Encoder", b ? new R2000MonitorFrameEncoder() : new ByteArrayEncoder())
                           .addLast("r2000Protocol", new R2000ProtocolHandler(this.getMessageCallBacks()));
               }
               // 注意:重要!单设备类型情况,手动设置了协议,请关闭自动切换功能!
               return false;
           });
       }

       return protocolSelect;
   }

双重保险,添加了协议自动切换开关和 Handler 名字。