Easy Netty 系列(六):异常处理详解

异常处理

摘要:异常处理在任何系统中都是重要的组成部分,Netty 的异常处理是通过在 ChannelHandler 中重写 exceptionCaught 方法来实现,这篇文章聚焦于此。

Netty 版本:4.1.70.Final

一、异常处理方式

1、捕获异常处理

    public static class InboundHandler extends ChannelInboundHandlerAdapter {
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            // 处理异常
            System.err.println(this.getClass().getSimpleName() + " ---- " + cause.getMessage());
            // 向下一个handler传递异常
            ctx.fireExceptionCaught(cause);
        }
    }

P.S. 传递异常将从下个 handler 一直往后传递,不会跳过 OutboundHandler。

2、通过监听 Funture、Promise 处理

1)在调研writewriteAndFlush可以获得得到 ChannelFeature,进而可以监听执行结果是否成功、异常。通常在 InboundHandler 中使用。

            ctx.executor().schedule(()->{
                channelFuture.addListener(new ChannelFutureListener() {
                    @Override
                    public void operationComplete(ChannelFuture future) throws Exception {
                        System.out.println("writeAndFlush done.");
                        if(future.isSuccess()){
                            System.out.println("send success.");
                        }else{
                       		// 处理异常
                            System.out.println("send fail!");
                        }
                    }
                });
            }, 0, TimeUnit.SECONDS);

2)在 OutboundHandler 中,write 方法的入参会带有个Promise参数,通过 Promise 对象可以处理异常,处理后将通知之前的 handler。使用时通过setSuccesssetFailure设置。

三、源码分析

1、是谁触发 exceptionCaught 方法

主要是 AbstractChannelhandlerContext#invokeExceptionCaught 方法。

    private void invokeExceptionCaught(final Throwable cause) {
        if (invokeHandler()) {
            try {
                //
                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);
        }
    }

当 channel 触发事件调用 invokeChannelActive、invokeChannelRead 过程中出现异常调用 invokeExceptionCaught。

    private void invokeChannelActive() {
        if (invokeHandler()) {
            try {
                ((ChannelInboundHandler) handler()).channelInactive(this);
            } catch (Throwable t) {
            	// 处理异常
                invokeExceptionCaught(t);
            }
        } else {
            fireChannelInactive();
        }
    }

    private void invokeChannelRead(Object msg) {
        if (invokeHandler()) {
            try {
                ((ChannelInboundHandler) handler()).channelRead(this, msg);
            } catch (Throwable t) {
            	// 处理异常
                invokeExceptionCaught(t);
            }
        } else {
            fireChannelRead(msg);
        }
    }

2、如果我们创建的 Handler 不处理异常,那么会由 Pipeline 中名为tail 的 ChannelHandlerContext 来捕获异常并打印。

可以看到 DefaultChannelPipeline 中有两个内部类:TailContext、HeadContext,最后的异常就是被 TailContext 的实力tail处理的,可以看到 onUnhandledInboundException 方法中使用 warn 级别输出了异常信息。

   // A special catch-all handler that handles both bytes and messages.
    final class TailContext extends AbstractChannelHandlerContext implements ChannelInboundHandler {

        TailContext(DefaultChannelPipeline pipeline) {
            super(pipeline, null, TAIL_NAME, TailContext.class);
            setAddComplete();
        }

        @Override
        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
            onUnhandledInboundUserEventTriggered(evt);
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            // 处理最后的异常,方法如下
            onUnhandledInboundException(cause);
        }
        // 省略...
    }

    /**
     * 处理最后的异常
     * Called once a {@link Throwable} hit the end of the {@link ChannelPipeline} without been handled by the user
     * in {@link ChannelHandler#exceptionCaught(ChannelHandlerContext, Throwable)}.
     */
    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);
        }
    }

    final class HeadContext extends AbstractChannelHandlerContext
            implements ChannelOutboundHandler, ChannelInboundHandler {

        private final Unsafe unsafe;

        HeadContext(DefaultChannelPipeline pipeline) {
            super(pipeline, null, HEAD_NAME, HeadContext.class);
            unsafe = pipeline.channel().unsafe();
            setAddComplete();
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            ctx.fireExceptionCaught(cause);
        }
        // 省略...
    }

EOF