NOTE

Exception Events

1. Prepare experiment data 2. Start debugging 3-7. Propagate the exception through handlers to TailContext

JavaCreated Updated 1 min readhistorical

This is a historical learning note and may contain outdated or incomplete understanding.

1. Prepare the Experiment Data

  • NettyServer
package com.example.server;

import com.example.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: <AUTHOR>
 * @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.example.server.handler;

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

/**
 * @description:
 * @author: <AUTHOR>
 * @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.example.server.handler;

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

/**
 * @description:
 * @author: <AUTHOR>
 * @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.example.server.handler;

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

/**
 * @description:
 * @author: <AUTHOR>
 * @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.example.server.handler;

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

import java.util.concurrent.TimeUnit;

/**
 * @description:
 * @author: <AUTHOR>
 * @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. Start Debugging

Set a breakpoint at com.example.server.handler.InboundHandlerA#exceptionCaught, start the server, and connect using nc.

3. First Caught by InboundHandlerA

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

After printing the message, the exception continues propagating through ctx.fireExceptionCaught.

3.1. Propagate the Exception Through AbstractChannelHandlerContext#fireExceptionCaught

io.netty.channel.AbstractChannelHandlerContext#fireExceptionCaught

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

next is InboundHandlerB, which means the exception is passed directly to the next node.

  • invokeExceptionCaught
static void invokeExceptionCaught(final AbstractChannelHandlerContext next, final Throwable cause) {
    ObjectUtil.checkNotNull(cause, "cause");
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
        // Here.
        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 {
            // Here.
            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);
    }
}

Execution at handler().exceptionCaught(this, cause) is actually entering InboundHandlerB.exceptionCaught.

4. Then Caught by InboundHandlerB

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

The logic is the same as in InboundHandlerA: after printing the exception, continue propagating it with ctx.fireExceptionCaught.

4.1. Propagate Through AbstractChannelHandlerContext#fireExceptionCaught

Same logic as above, except this time the next node is OutboundHandlerA.

5. Then Caught by OutboundHandlerA

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

The logic is the same as in InboundHandlerA: after printing the exception, continue propagating it with ctx.fireExceptionCaught.

5.1. Propagate Through AbstractChannelHandlerContext#fireExceptionCaught

Same logic as above, except this time the next node is OutboundHandlerB.

6. Then Caught by OutboundHandlerB

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

The logic is the same as in InboundHandlerA: after printing the exception, continue propagating it with ctx.fireExceptionCaught.

6.1. Propagate Through AbstractChannelHandlerContext#fireExceptionCaught

Same logic as above, except this time the next node is io.netty.channel.DefaultChannelPipeline.TailContext#exceptionCaught.

7. Finally Caught by 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);
    }
}

If the exception keeps propagating all the way to tail, a warning is logged and the resource is released at the end.

Discussion

Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub