NOTE
Exception Events
1. Prepare experiment data 2. Start debugging 3-7. Propagate the exception through handlers to TailContext
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