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第十六课——ascii码

    2.ascii码 美国设计出来的一张编码表,将涉及的字符都编号了,底层仍然还是进行二进制的运算: 记住:3个范围段 1).'0' --> 码值:48 2).'A' --> 码值:65 3) ...

  2. OSM

    一.OSM是什么 开放街道图(OpenStreetMap,简称OSM)是一个网上地图协作计划,目标是创造一个内容自由且能让所有人编辑的世界地图(wiki:http://wiki.openstreetm ...

  3. Java并发编程--5.信号量和障碍器

    Semaphore信号量 简介 它本质上是一个共享锁,限制访问公共资源的线程数目,它也被称为计数信号量acquire()许可一个线程, Semaphore – 1; 没有可用的许可时,Semaphor ...

  4. linq to sql 分页技术

    昨天在用LINQ写分页的时候碰到一个很奇怪的问题:翻页的时候,有的数据会莫名其妙地消失,查了半个多小时才发现问题所在,其实是一个很细节的地方. 数据表如下: LINQ分页的实现是: var artic ...

  5. _tcschr和_tcsrchr使用

    好处:是可以不管是用unicode 编码还是其他 ,代码都不用改. C++标准库函数提供了字符和字符串的操作函数,并提供了其UNICODE版本,如: 1._tcschr代替strchr或者wcschr ...

  6. STM32定时器输出PWM频率和步进电机控制速度计算

    1.STM32F4系列定时器输出PWM频率计算 第一步,了解定时器的时钟多少: 我们知道AHP总线是168Mhz的频率,而APB1和APB2都是挂在AHP总线上的. (1)高级定时器timer1, t ...

  7. VSC 插件开发从入门到Hello World

    1.原理放一边,我们先来个Hello,World 1.1 安装基础环境 需要的基础环境列表: Node.js npm vs code yo generator-code yo:全称Yeoman,可以把 ...

  8. 很清晰的解读i2c协议

    很清晰的解读i2c协议 转载:http://dpinglee.blog.163.com/blog/static/14409775320112239374615/ 1.I2C协议 2条双向串行线,一条数 ...

  9. Missing artifact com.sun:tools:jar:1.5.0解决的方法

    前一阵子下了最新的JavaEE版本号的eclipse,导入mavenproject之后,pom文件一直报Missing artifact com.sun:tools:jar:1.5.0.非常纳闷,to ...

  10. p​o​s​t​m​a​r​k​使​用

    一.Postmark原理 Postmark是由着名的NAS提供商NetApp开发,用来测试其产品的后端存储性能. Postmark主要用于测试文件系统在邮件系统或电子商务系统中性能,这类应用的特点是: ...