netty源碼分析(23)- 異常的傳播

上一節研究了outbound事件的傳播過程,是和ChannelOutboundHandler的添加順序相反的。從pipeline或者channel調用outbound事件的傳播方法,是從TailContext開始傳播,直到HeadContext,最終頭節點使用unsafe處理具體的事件。

本節學習異常的傳播,添加了inboundoutbound處理器,其順序是iA iB iC oA oB oC,並覆蓋了exceptionCaught異常處理方法。在iB處理器中拋出了一個自定義異常,跟蹤。

class DataServerInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {

        ch.pipeline().addLast(
                new InboundHandlerA(),
                new InboundHandlerB(),
                new InboundHandlerC(),
                new OutboundHandlerA(),
                new OutboundHandlerB(),
                new OutboundHandlerC()
        );
    }
}


class OutboundHandlerA extends ChannelOutboundHandlerAdapter {

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("OutboundHandlerA.exceptionCaught()");
        ctx.fireExceptionCaught(cause);
    }
}

class OutboundHandlerB extends ChannelOutboundHandlerAdapter {

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("OutboundHandlerB.exceptionCaught()");
        ctx.fireExceptionCaught(cause);
    }
}

class OutboundHandlerC extends ChannelOutboundHandlerAdapter {

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("OutboundHandlerC.exceptionCaught()");
        ctx.fireExceptionCaught(cause);
    }
}


class InboundHandlerA extends ChannelInboundHandlerAdapter {

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("InboundHandlerA.exceptionCaught()");
        ctx.fireExceptionCaught(cause);
    }
}

class InboundHandlerB extends ChannelInboundHandlerAdapter {


    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        throw new BusinessException("form InboundHandlerB");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("InboundHandlerB.exceptionCaught()");
        ctx.fireExceptionCaught(cause);
    }
}

class InboundHandlerC extends ChannelInboundHandlerAdapter {


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("InboundHandlerC.exceptionCaught()");
        ctx.fireExceptionCaught(cause);
    }

}

從打印的信息可以猜測出,異常的傳播似乎是順序傳播的。


  • 通過端點跟中捕獲異常的地方,發現經過notifyHandlerException,invokeExceptionCaught最終handler().exceptionCaught(this, cause);調用了用戶異常處理回調函數,交由用戶本身進行處理。
    private void invokeChannelRead(Object msg) {
        if (invokeHandler()) {
            try {
                //獲取對應的handler並調用channelRead方法
                ((ChannelInboundHandler) handler()).channelRead(this, msg);
            } catch (Throwable t) {
                //喚醒異常處理
                notifyHandlerException(t);
            }
        } else {
            fireChannelRead(msg);
        }
    }


    private void notifyHandlerException(Throwable cause) {
        if (inExceptionCaught(cause)) {
            if (logger.isWarnEnabled()) {
                logger.warn(
                        "An exception was thrown by a user handler " +
                                "while handling an exceptionCaught event", cause);
            }
            return;
        }
        //異常處理
        invokeExceptionCaught(cause);
    }

    private void invokeExceptionCaught(final Throwable cause) {
        if (invokeHandler()) {
            try {
                //調用異常處理回調函數exceptionCaught
                handler().exceptionCaught(this, cause);
            } catch (Throwable error) {
                if (logger.isDebugEnabled()) {
                    logger.debug(
                        "An exception {}" +
                        "was thrown by a user handler's exceptionCaught() " +
                        "method while handling the following exception:",
                        ThrowableUtil.stackTraceToString(error), cause);
                } else if (logger.isWarnEnabled()) {
                    logger.warn(
                        "An exception '{}' [enable DEBUG level for full stacktrace] " +
                        "was thrown by a user handler's exceptionCaught() " +
                        "method while handling the following exception:", error, cause);
                }
            }
        } else {
            fireExceptionCaught(cause);
        }
    }

  • 跟蹤傳播方法ctx.fireExceptionCaught(cause);,我們知道傳播異常時通過fireExceptionCaught方法。發現直接待用了當前節點的下一個節點。接下來的過程和上述一致。
    @Override
    public ChannelHandlerContext fireExceptionCaught(final Throwable cause) {
        //直接使用鏈表中的下一個節點
        invokeExceptionCaught(next, cause);
        return this;
    }

    static void invokeExceptionCaught(final AbstractChannelHandlerContext next, final Throwable cause) {
        ObjectUtil.checkNotNull(cause, "cause");
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            //執行異常回調方法
            next.invokeExceptionCaught(cause);
        } else {
            try {
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        next.invokeExceptionCaught(cause);
                    }
                });
            } catch (Throwable t) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Failed to submit an exceptionCaught() event.", t);
                    logger.warn("The exceptionCaught() event that was failed to submit was:", cause);
                }
            }
        }
    }

補充:ctx.pipeline().fireExceptionCaught(cause);是從頭節點開始傳播的。

    ctx.pipeline().fireExceptionCaught(cause);

    @Override
    public final ChannelPipeline fireExceptionCaught(Throwable cause) {
        //從頭節點開始傳播
        AbstractChannelHandlerContext.invokeExceptionCaught(head, cause);
        return this;
    }
  • 最終異常如果一直傳播下去的話則會到達尾節點,打出警告日誌,同時釋放內存。
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        onUnhandledInboundException(cause);
    }


    protected void onUnhandledInboundException(Throwable cause) {
        try {
            logger.warn(
                    "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.",
                    cause);
        } finally {
            ReferenceCountUtil.release(cause);
        }
    }
  • 異常處理建議
    根據異常處理的特點,在尾部增加一個異常處理器,統一處理所有的異常,處理完成之後並不往下進行傳播
class DataServerInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {

        ch.pipeline().addLast(
                new InboundHandlerA(),
                new InboundHandlerB(),
                new InboundHandlerC(),
                new OutboundHandlerA(),
                new OutboundHandlerB(),
                new OutboundHandlerC(),
                new ExceptionCaughtHandler()
        );
        
    }
}


class ExceptionCaughtHandler extends ChannelInboundHandlerAdapter{
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        if (cause instanceof BusinessException) {
            System.out.println("is BusinessException");
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章