NOTE

2.92 异常事件

1. 准备实验数据 - NettyServer - InboundHandlerA - InboundHandlerB - OutboundHandlerA - OutboundHandlerB 2. 开始debug 在 com.zsk.server.handler.InboundHandlerA#

Java创建于 更新于 historical

这是历史学习笔记,可能存在过时或不完整的理解。

1. 准备实验数据

  • NettyServer
package com.zsk.server;

import com.zsk.server.handler.*;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

/**
 * @description:
 * @author: zsk
 * @create: 2019-12-05 19:14
 **/
public class NettyServer
{
    public static void main(String[] args)
    {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try
        {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup,workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 100)
                    .childHandler(new ChannelInitializer<SocketChannel>()
                    {
                        @Override
                        protected void initChannel(SocketChannel channel) throws Exception
                        {
                            ChannelPipeline pipeline = channel.pipeline();
                            pipeline.addLast(new InboundHandlerA())
                                    .addLast(new InboundHandlerB())
                                    .addLast(new OutboundHandlerA())
                                    .addLast(new OutboundHandlerB());
                        }
                    });

            ChannelFuture future = bootstrap.bind(8000).sync();

            future.channel().closeFuture().sync();
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        finally
        {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
  • InboundHandlerA
package com.zsk.server.handler;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
 * @description:
 * @author: zsk
 * @create: 2019-12-10 20:41
 **/
public class InboundHandlerA extends ChannelInboundHandlerAdapter
{
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception
    {
        throw new RuntimeException("custom error");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception
    {
        System.out.println(this.getClass().getSimpleName() + "catch exception:" + cause.getMessage());
        ctx.fireExceptionCaught(cause);
    }
}
  • InboundHandlerB
package com.zsk.server.handler;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
 * @description:
 * @author: zsk
 * @create: 2019-12-10 20:41
 **/
public class InboundHandlerB extends ChannelInboundHandlerAdapter
{

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception
    {
        System.out.println(this.getClass().getSimpleName() + "catch exception:" + cause.getMessage());
        ctx.fireExceptionCaught(cause);
    }
}
  • OutboundHandlerA
package com.zsk.server.handler;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;

/**
 * @description:
 * @author: zsk
 * @create: 2019-12-10 23:08
 **/
public class OutboundHandlerA extends ChannelOutboundHandlerAdapter
{
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception
    {
        System.out.println(this.getClass().getSimpleName() + "catch exception:" + cause.getMessage());
        ctx.fireExceptionCaught(cause);
    }
}
  • OutboundHandlerB
package com.zsk.server.handler;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;

import java.util.concurrent.TimeUnit;

/**
 * @description:
 * @author: zsk
 * @create: 2019-12-10 23:08
 **/
public class OutboundHandlerB extends ChannelOutboundHandlerAdapter
{
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception
    {
        System.out.println(this.getClass().getSimpleName() + "catch exception:" + cause.getMessage());
        ctx.fireExceptionCaught(cause);
    }
}

2. 开始debug

com.zsk.server.handler.InboundHandlerA#exceptionCaught上打个断点启动,然后使用nc连接

3. 首先被InboundHandlerA捕获

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception
{
    System.out.println(this.getClass().getSimpleName() + "catch exception:" + cause.getMessage());
    ctx.fireExceptionCaught(cause);
}

打印消息之后,通过ctx.fireExceptionCaught继续往后传播异常

3.1. 通过AbstractChannelHandlerContext#fireExceptionCaught传播异常

io.netty.channel.AbstractChannelHandlerContext#fireExceptionCaught

public ChannelHandlerContext fireExceptionCaught(final Throwable cause) {
	//这里
    invokeExceptionCaught(next, cause);
    return this;
}

next是InboundHandlerB,说明直接把异常丢给下一个节点

  • invokeExceptionCaught
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);
            }
        }
    }
}
  • 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);
    }
}

走到handler().exceptionCaught(this, cause);其实就是进入了InboundHandlerB的exceptionCaught方法

4. 然后被InboundHandlerB捕获

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception
{
    System.out.println(this.getClass().getSimpleName() + "catch exception:" + cause.getMessage());
    ctx.fireExceptionCaught(cause);
}

逻辑同InboundHandlerA,打印异常后,继续通过ctx.fireExceptionCaught继续往后传播异常

4.1. 通过AbstractChannelHandlerContext#fireExceptionCaught传播异常

同上面的逻辑,只不过这次的下一个节点是OutboundHandlerA

5. 接着被OutboundhandlerA捕获

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception
{
    System.out.println(this.getClass().getSimpleName() + "catch exception:" + cause.getMessage());
    ctx.fireExceptionCaught(cause);
}

逻辑同InboundHandlerA,打印异常后,继续通过ctx.fireExceptionCaught继续往后传播异常

5.1. 通过AbstractChannelHandlerContext#fireExceptionCaught传播异常

同上面的逻辑,只不过这次的下一个节点是OutboundHandlerB

6. 再被OutboundHandlerB捕获

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception
{
    System.out.println(this.getClass().getSimpleName() + "catch exception:" + cause.getMessage());
    ctx.fireExceptionCaught(cause);
}

逻辑同InboundHandlerA,打印异常后,继续通过ctx.fireExceptionCaught继续往后传播异常

6.1. 通过AbstractChannelHandlerContext#fireExceptionCaught传播异常

同上面的逻辑,只不过这次的下一个节点是io.netty.channel.DefaultChannelPipeline.TailContext#exceptionCaught

7. 最后被TailContext捕获

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    onUnhandledInboundException(cause);
}
  • onUnhandledInboundException
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);
    }
}

如果异常一直被传播到tail,那么会打印警告信息,最后释放资源2