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

作者:向南是个万人迷 时间:2021-08-15 16:12:02 

讲完了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);
}

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

来源:https://www.cnblogs.com/xiangnan6122/p/10204484.html

标签:Netty,分布式,pipeline,异常
0
投稿

猜你喜欢

  • Android WindowManger的层级分析详解

    2023-08-05 23:51:40
  • nacos中的配置使用@Value注解获取不到值的原因及解决方案

    2023-11-29 13:43:00
  • 通过Docker启动Solace并在Spring Boot通过JMS整合Solace的操作方法

    2023-07-11 09:52:28
  • 用SpringBoot+Vue+uniapp小程序实现在线房屋装修管理系统

    2023-11-12 04:10:48
  • springboot多环境配置文件及自定义配置文件路径详解

    2021-09-30 03:55:54
  • Java基于Socket实现网络编程实例详解

    2023-11-23 12:22:37
  • Java面试之动态规划与组合数

    2023-11-24 21:20:52
  • 值得收藏的2017年Java开发岗位面试题

    2023-11-29 15:22:01
  • Java常用类String的面试题汇总(java面试题)

    2023-11-23 20:40:45
  • mybatis多个区间处理方式(双foreach循环)

    2023-11-26 09:01:42
  • java 实现发短信功能---腾讯云短信

    2023-11-29 11:03:49
  • 从最基本的Java工程搭建SpringMVC+SpringDataJPA+Hibernate

    2023-05-31 20:37:20
  • Android使用Gradle依赖配置compile、implementation与api的区别介绍

    2023-09-27 18:11:40
  • SSM Mapper文件查询出返回数据查不到个别字段的问题

    2023-10-17 01:02:08
  • jQuery 动画效果代码分享

    2023-11-24 00:10:12
  • lambda表达式解决java后台分组排序过程解析

    2023-11-29 06:03:39
  • C语言文件操作函数大全(超详细)

    2023-07-15 06:20:42
  • java创建多级目录文件的实例讲解

    2023-11-27 09:29:02
  • 一篇文章带你深入了解Java封装

    2023-11-20 00:37:45
  • flutter实现appbar下选项卡切换

    2023-06-21 13:35:24
  • asp之家 软件编程 m.aspxhome.com