Netty分布式pipeline管道异常传播事件源码解析

目录
  • 传播异常事件
    • 简单的异常处理的场景
      • 我们跟到invokeChannelRead这个方法
    • 我还是通过两种写法来进行剖析
      • 跟进invokeExceptionCaught方法
      • 跟到invokeExceptionCaught方法中

讲完了inbound事件和outbound事件的传输流程, 这一小节剖析异常事件的传输流程

传播异常事件

简单的异常处理的场景

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    throw new Exception("throw Exception");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    System.out.println(cause.getMessage());
}

我们在handler的channelRead方法中主动抛出异常, 模拟程序中出现异常的场景, 经测试会发现, 程序最终会走到exceptionCaught方法中, 获取异常对象并打印其信息

那么抛出异常之后, 是如何走到exceptionCaught方法的呢?

我们回顾之前小节channelRead事件的传播流程, channelRead方法是在AbstractChannelHandlerContext类的invokeChannelRead方法中被调用

我们跟到invokeChannelRead这个方法

private void invokeChannelRead(Object msg) {
    if (invokeHandler()) {
        try {
            //调用了当前handler的channelRead方法, 其实就是head对象调用自身的channelRead方法
            ((ChannelInboundHandler) handler()).channelRead(this, msg);
        } catch (Throwable t) {
            //发生异常的时候在这里捕获异常
            notifyHandlerException(t);
        }
    } else {
        fireChannelRead(msg);
    }
}

这里不难看出, 当调用户自定义的handler的channelRead方法发生异常之后, 会被捕获, 并调用notifyHandlerException方法, 并传入异常对象, 也就是我们示例中抛出的异常

我们跟到fireChannelRead方法中:

private void notifyHandlerException(Throwable cause) {
    //代码省略
    invokeExceptionCaught(cause);
}

再继续跟到invokeExceptionCaught方法中:

private void invokeExceptionCaught(final Throwable cause) {
    if (invokeHandler()) {
        try {
            //当前handler调用exceptionCaught()方法
            handler().exceptionCaught(this, cause);
        } catch (Throwable error) {
            //代码省略
        }
    } else {
        fireExceptionCaught(cause);
    }
}

走到这里一切都明白了, 这里调用了当前handler的exceptionCaught方法, 也就是我们重写的exceptionCaught方法

知道了为什么会走到exceptionCaught方法之后, 我们再进行剖析异常事件的传播流程

我还是通过两种写法来进行剖析

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    System.out.println(cause.getMessage());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    //写法1
    ctx.fireChannelRead(cause);
    //写法2
    ctx.pipeline().fireExceptionCaught(cause);
}

这两种写法我们并不陌生, 可能我们能直接猜到, 第一种写法是从当前节点进行传播, 第二种写法则从头结点或者尾节点进行转播, 那么和传播inbound事件或outbound事件有什么区别呢?我们先以第二种写法为例, 剖析异常事件传输的整个流程

跟到DefualtChannelPipeline的fireExceptionCaught方法中:

public final ChannelPipeline fireExceptionCaught(Throwable cause) {
    AbstractChannelHandlerContext.invokeExceptionCaught(head, cause);
    return this;
}

我们看到invokeExceptionCaught传入了head节点, 我们可以猜测, 异常事件的传播是从head节点开始的

跟进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) {
            //忽略代码
        }
    }
}

因为这里是传入的是head节点, 所以这里的next指向head节点

我们跟到invokeExceptionCaught方法中, 这里其实是headContext的父类AbstractChannelHandlerContext中的方法:

private void invokeExceptionCaught(final Throwable cause) {
    if (invokeHandler()) {
        try {
            //当前handler调用exceptionCaught()方法
            handler().exceptionCaught(this, cause);
        } catch (Throwable error) {
            //代码省略
        }
    } else {
        fireExceptionCaught(cause);
    }
}

这里又是我们熟悉的逻辑, 调用当前handler的exceptionCaught方法, 因为当前handler是head, 所以首先会调用headContext的exceptionCaught方法

跟进exceptionCaught方法:

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    ctx.fireExceptionCaught(cause);
}

这里仅仅是继续传播异常事件, 这时候我们发现, 这个写法和我们刚才提到传播异常事件的两种写法的第一种写法一样:

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    //写法1
    ctx.fireChannelRead(cause);
    //写法2
    ctx.pipeline().fireExceptionCaught(cause);
}

根据我们之前的学习, 我们知道第一种写法是从当前节点传播, 而第二种写法是从头传播, 并且要求传播事件一定要使用第一种写法, 否则事件到这里会重新从头传播进而引发不可预知错误, 这个结论在异常传播同样适用, 同学们一定要注意这点

我们继续跟fireExceptionCaught方法, 这里会走到AbstractChannelHandlerContex类的fireExceptionCaught方法:

public ChannelHandlerContext fireExceptionCaught(final Throwable cause) {
    //传播异常事件的时候, 直接拿了当前节点的下一个节点
    invokeExceptionCaught(next, cause);
    return this;
}

这个时候我们发现, 这里并没有去获取下一个的inbound节点还是outbound节点, 而是直接通过next拿到下一个节点, 这就说明在异常事件传播的过程中是不区分inbound事件还是outbound事件的, 都是直接从head节点按照链表结构往下传播,

跟到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) {
            //代码省略
        }
    }
}

这里又是我们熟悉的逻辑, 我们知道invokeExceptionCaught中执行了next的exceptionCaught, 这里的next, 因为我们是从head节点开始剖析的, 所以这里很有可能就是用户自定义的handler, 如果用户没有重写exceptionCaught方法, 则会交给用户handler的父类处理

我们以ChannelInboundHandlerAdapter为例看它的该方法实现:

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
        throws Exception {
    ctx.fireExceptionCaught(cause);
}

我们看到这里继续向下传播了异常事件

走到这里我们会知道, 如果我们没有重写exceptionCaught方法, 异常事件会一直传播到链表的底部, 就是tail节点

我们跟到TailConext的exceptionCaught方法:

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    onUnhandledInboundException(cause);
}

我们看到最终这里释放了异常对象

以上就是有关异常事件的传播,更多关于Netty分布式pipeline管道异常传播的资料请关注我们其它相关文章!

(0)

相关推荐

  • Netty分布式pipeline传播inbound事件源码分析

    前一小结回顾:pipeline管道Handler删除 传播inbound事件 有关于inbound事件, 在概述中做过简单的介绍, 就是以自己为基准, 流向自己的事件, 比如最常见的channelRead事件, 就是对方发来数据流的所触发的事件, 己方要对这些数据进行处理, 这一小节, 以激活channelRead为例讲解有关inbound事件的处理流程 在业务代码中, 我们自己的handler往往会通过重写channelRead方法来处理对方发来的数据, 那么对方发来的数据是如何走到chann

  • Netty分布式pipeline管道Handler的添加代码跟踪解析

    目录 添加handler 我们跟到其addLast()方法中 再继续跟到addLast()方法中去 我们跟到checkMultiplicity(handler)中 跟到filterName方法中 跟到isInbound(handler)方法中 我们回到最初的addLast()方法中 我们跟进addLast0(newCtx)中 前文传送门:Netty分布式pipeline管道创建 添加handler 我们以用户代码为例进行剖析: .childHandler(new ChannelInitializ

  • Netty分布式pipeline管道创建方法跟踪解析

    目录 概述 pipeline的创建 上一章节回顾:Netty分布式源码分析监听读事件 概述 pipeline, 顾名思义, 就是管道的意思, 在netty中, 事件在pipeline中传输, 用户可以中断事件, 添加自己的事件处理逻辑, 可以直接将事件中断不再往下传输, 同样可以改变管道的流向, 传递其他事件.这里有点类似于Spring的AOP, 但是比AOP实现起来简单的多 事件通常分为两种, 一是inBound事件, 另一种是outBound事件, inBound事件, 顾名思义, 就是从另

  • Netty分布式源码分析监听读事件

    前文传送门:NioSocketChannel注册到selector 我们回到AbstractUnsafe的register0()方法: private void register0(ChannelPromise promise) { try { //省略代码 //做实际的注册 doRegister(); neverRegistered = false; registered = true; //触发事件 pipeline.invokeHandlerAddedIfNeeded(); safeSet

  • Netty分布式pipeline管道传播outBound事件源码解析

    目录 outbound事件传输流程 这里我们同样给出两种写法 跟到其write方法中: 跟到findContextOutbound中 回到write方法: 继续跟invokeWrite0 我们跟到HeadContext的write方法中 了解了inbound事件的传播过程, 对于学习outbound事件传输的流程, 也不会太困难 outbound事件传输流程 在我们业务代码中, 有可能使用wirte方法往写数据: public void channelActive(ChannelHandlerC

  • Netty分布式pipeline管道异常传播事件源码解析

    目录 传播异常事件 简单的异常处理的场景 我们跟到invokeChannelRead这个方法 我还是通过两种写法来进行剖析 跟进invokeExceptionCaught方法 跟到invokeExceptionCaught方法中 讲完了inbound事件和outbound事件的传输流程, 这一小节剖析异常事件的传输流程 传播异常事件 简单的异常处理的场景 @Override public void channelRead(ChannelHandlerContext ctx, Object msg

  • Netty分布式pipeline管道传播事件的逻辑总结分析

    目录 问题分析 首先完成了handler的添加, 但是并没有马上执行回调 回到callHandlerCallbackLater方法中 章节总结 我们在第一章和第三章中, 遗留了很多有关事件传输的相关逻辑, 这里带大家一一回顾 问题分析 首先看两个问题: 1.在客户端接入的时候, NioMessageUnsafe的read方法中pipeline.fireChannelRead(readBuf.get(i))为什么会调用到ServerBootstrap的内部类ServerBootstrapAccep

  • Netty分布式pipeline管道Handler的删除逻辑操作

    目录 删除handler操作 我们跟到getContextPrDie这个方法中 首先要断言删除的节点不能是tail和head 回到remove(ctx)方法 上一小节我们学习了添加handler的逻辑操作, 这一小节我们学习删除handler的相关逻辑 删除handler操作 如果用户在业务逻辑中进行ctx.pipeline().remove(this)这样的写法, 或者ch.pipeline().remove(new SimpleHandler())这样的写法, 则就是对handler进行删除

  • Netty分布式ByteBuf的分类方式源码解析

    目录 ByteBuf根据不同的分类方式 会有不同的分类结果 1.Pooled和Unpooled 2.基于直接内存的ByteBuf和基于堆内存的ByteBuf 3.safe和unsafe 上一小节简单介绍了AbstractByteBuf这个抽象类, 这一小节对其子类的分类做一个简单的介绍 ByteBuf根据不同的分类方式 会有不同的分类结果 我们首先看第一种分类方式 1.Pooled和Unpooled pooled是从一块内存里去取一段连续内存封装成byteBuf 具体标志是类名以Pooled开头

  • Netty分布式客户端接入流程初始化源码分析

    目录 前文概述: 第一节:初始化NioSockectChannelConfig 创建channel 跟到其父类DefaultChannelConfig的构造方法中 再回到AdaptiveRecvByteBufAllocator的构造方法中 继续跟到ChannelMetadata的构造方法中 回到DefaultChannelConfig的构造方法 前文概述: 之前的章节学习了server启动以及eventLoop相关的逻辑, eventLoop轮询到客户端接入事件之后是如何处理的?这一章我们循序渐

  • Netty组件NioEventLoopGroup创建线程执行器源码解析

    目录 通过上一章的学习, 我们了解了Server启动的大致流程, 有很多组件与模块并没有细讲, 从这个章开始, 我们开始详细剖析netty的各个组件, 并结合启动流程, 将这些组件的使用场景及流程进行一个详细的说明 这一章主要学习NioEventLoop相关的知识, 何为NioEventLoop? NioEventLoop是netty的一个线程, 在上一节我们创建两个NioEventLoopGroup: EventLoopGroup bossGroup = new NioEventLoopGro

随机推荐