Netty源码分析第四章: pipeline

第6节: 传播异常事件

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

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

以上就是有关异常事件的传播

上一节: 传播outbound事件

下一节: 前章节内容回顾

Netty源码分析第4章(pipeline)---->第6节: 传播异常事件的更多相关文章

  1. Netty源码分析第4章(pipeline)---->第4节: 传播inbound事件

    Netty源码分析第四章: pipeline 第四节: 传播inbound事件 有关于inbound事件, 在概述中做过简单的介绍, 就是以自己为基准, 流向自己的事件, 比如最常见的channelR ...

  2. Netty源码分析第4章(pipeline)---->第5节: 传播outbound事件

    Netty源码分析第五章: pipeline 第五节: 传播outBound事件 了解了inbound事件的传播过程, 对于学习outbound事件传输的流程, 也不会太困难 在我们业务代码中, 有可 ...

  3. Netty源码分析第4章(pipeline)---->第7节: 前章节内容回顾

    Netty源码分析第四章: pipeline 第七节: 前章节内容回顾 我们在第一章和第三章中, 遗留了很多有关事件传输的相关逻辑, 这里带大家一一回顾 首先看两个问题: 1.在客户端接入的时候, N ...

  4. Netty源码分析第4章(pipeline)---->第1节: pipeline的创建

    Netty源码分析第四章: pipeline 概述: pipeline, 顾名思义, 就是管道的意思, 在netty中, 事件在pipeline中传输, 用户可以中断事件, 添加自己的事件处理逻辑, ...

  5. Netty源码分析第4章(pipeline)---->第2节: handler的添加

    Netty源码分析第四章: pipeline 第二节: Handler的添加 添加handler, 我们以用户代码为例进行剖析: .childHandler(new ChannelInitialize ...

  6. Netty源码分析第4章(pipeline)---->第3节: handler的删除

    Netty源码分析第四章: pipeline 第三节: handler的删除 上一小节我们学习了添加handler的逻辑操作, 这一小节我们学习删除handler的相关逻辑 如果用户在业务逻辑中进行c ...

  7. Netty源码分析第2章(NioEventLoop)---->第7节: 处理IO事件

    Netty源码分析第二章: NioEventLoop   第七节:处理IO事件 上一小节我们了解了执行select()操作的相关逻辑, 这一小节我们继续学习select()之后, 轮询到io事件的相关 ...

  8. Netty源码分析第5章(ByteBuf)---->第10节: SocketChannel读取数据过程

    Netty源码分析第五章: ByteBuf 第十节: SocketChannel读取数据过程 我们第三章分析过客户端接入的流程, 这一小节带大家剖析客户端发送数据, Server读取数据的流程: 首先 ...

  9. Netty源码分析第5章(ByteBuf)---->第4节: PooledByteBufAllocator简述

    Netty源码分析第五章: ByteBuf 第四节: PooledByteBufAllocator简述 上一小节简单介绍了ByteBufAllocator以及其子类UnPooledByteBufAll ...

随机推荐

  1. Python基础.md

    数据结构 列表 访问 list1 = ['java','C','C++','Python'] In [10]: list1[1] Out[10]: 'C' In [11]: list1[-1] Out ...

  2. javascript:typeof与instanceof区别

    from:http://www.wxwdesign.cn/article/skills/javascript_typeof_instanceof.htm JavaScript中typeof和insta ...

  3. Python自动化之form验证二

    class LoginForm(forms.Form): user = fields.CharField() pwd = fields.CharField(validators=[]) def cle ...

  4. C++——命名空间

    C++命名空间基本常识 所谓namespace,是指标识符的各种可见范围.C++标准程序库中的所有标识符都被定义于一个名为std的namespace中. 一 :<iostream>和< ...

  5. java基础二 java的跨平台特性

    一:java跨平台的特性: 1.生成不平台无关系的字节码. 2.通过和平台有关的jvm即java虚拟机来执行字节码.jvm不跨平台. 图示: 疑问:1.为什么我们不直接写字节码? 因为字节码只有jvm ...

  6. Spring源码分析(二十四)初始化非延迟加载单例

    摘要: 本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 完成BeanFactory的初始化工作,其中包括ConversionS ...

  7. PAT乙级1017

    1017 A除以B (20 分)   本题要求计算 A/B,其中 A 是不超过 1000 位的正整数,B 是 1 位正整数.你需要输出商数 Q 和余数 R,使得 A=B×Q+R 成立. 输入格式: 输 ...

  8. [NOIp2015]运输计划 (二分 $+$ 树上差分)

    #\(\mathcal{\color{red}{Description}}\) \(Link\) 在一棵带有边权的树上,可以选择使一条边权为零.然后对于所有\(M\)条链,使其链长最大值最小. #\( ...

  9. 在ubuntu中添加widows启动项的简单方法

    打开终端执行: sudo vim /boot/grub/grub.cfg 或者 sudo gedit /boot/grub/grub.cfg 打开文件grub.cfg. 在文件中添加:添加的时候注意和 ...

  10. CAN总线实际运用分析问题。

    组态设计   人机交互  上位机  分布式控制系统  下位机  (单片机/PLC)  CAN总线用线缆   连接方式(手牵手,T型)    CAN总线接地(大地) http://bbs.gongkon ...