上一節研究了outbound
事件的傳播過程,是和ChannelOutboundHandler
的添加順序相反的。從pipeline
或者channel
調用outbound
事件的傳播方法,是從TailContext
開始傳播,直到HeadContext
,最終頭節點使用unsafe
處理具體的事件。
本節學習異常的傳播,添加了inbound
和outbound
處理器,其順序是iA iB iC oA oB oC
,並覆蓋了exceptionCaught
異常處理方法。在iB
處理器中拋出了一個自定義異常,跟蹤。
class DataServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(
new InboundHandlerA(),
new InboundHandlerB(),
new InboundHandlerC(),
new OutboundHandlerA(),
new OutboundHandlerB(),
new OutboundHandlerC()
);
}
}
class OutboundHandlerA extends ChannelOutboundHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("OutboundHandlerA.exceptionCaught()");
ctx.fireExceptionCaught(cause);
}
}
class OutboundHandlerB extends ChannelOutboundHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("OutboundHandlerB.exceptionCaught()");
ctx.fireExceptionCaught(cause);
}
}
class OutboundHandlerC extends ChannelOutboundHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("OutboundHandlerC.exceptionCaught()");
ctx.fireExceptionCaught(cause);
}
}
class InboundHandlerA extends ChannelInboundHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("InboundHandlerA.exceptionCaught()");
ctx.fireExceptionCaught(cause);
}
}
class InboundHandlerB extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
throw new BusinessException("form InboundHandlerB");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("InboundHandlerB.exceptionCaught()");
ctx.fireExceptionCaught(cause);
}
}
class InboundHandlerC extends ChannelInboundHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("InboundHandlerC.exceptionCaught()");
ctx.fireExceptionCaught(cause);
}
}
從打印的信息可以猜測出,異常的傳播似乎是順序傳播的。
- 通過端點跟中捕獲異常的地方,發現經過
notifyHandlerException
,invokeExceptionCaught
最終handler().exceptionCaught(this, cause);
調用了用戶異常處理回調函數,交由用戶本身進行處理。
private void invokeChannelRead(Object msg) {
if (invokeHandler()) {
try {
//獲取對應的handler並調用channelRead方法
((ChannelInboundHandler) handler()).channelRead(this, msg);
} catch (Throwable t) {
//喚醒異常處理
notifyHandlerException(t);
}
} else {
fireChannelRead(msg);
}
}
private void notifyHandlerException(Throwable cause) {
if (inExceptionCaught(cause)) {
if (logger.isWarnEnabled()) {
logger.warn(
"An exception was thrown by a user handler " +
"while handling an exceptionCaught event", cause);
}
return;
}
//異常處理
invokeExceptionCaught(cause);
}
private void invokeExceptionCaught(final Throwable cause) {
if (invokeHandler()) {
try {
//調用異常處理回調函數exceptionCaught
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);
}
}
- 跟蹤傳播方法
ctx.fireExceptionCaught(cause);
,我們知道傳播異常時通過fireExceptionCaught方法
。發現直接待用了當前節點的下一個節點。接下來的過程和上述一致。
@Override
public ChannelHandlerContext fireExceptionCaught(final Throwable cause) {
//直接使用鏈表中的下一個節點
invokeExceptionCaught(next, cause);
return this;
}
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) {
if (logger.isWarnEnabled()) {
logger.warn("Failed to submit an exceptionCaught() event.", t);
logger.warn("The exceptionCaught() event that was failed to submit was:", cause);
}
}
}
}
補充:ctx.pipeline().fireExceptionCaught(cause);
是從頭節點開始傳播的。
ctx.pipeline().fireExceptionCaught(cause);
@Override
public final ChannelPipeline fireExceptionCaught(Throwable cause) {
//從頭節點開始傳播
AbstractChannelHandlerContext.invokeExceptionCaught(head, cause);
return this;
}
- 最終異常如果一直傳播下去的話則會到達尾節點,打出警告日誌,同時釋放內存。
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
onUnhandledInboundException(cause);
}
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);
}
}
- 異常處理建議
根據異常處理的特點,在尾部增加一個異常處理器,統一處理所有的異常,處理完成之後並不往下進行傳播
class DataServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(
new InboundHandlerA(),
new InboundHandlerB(),
new InboundHandlerC(),
new OutboundHandlerA(),
new OutboundHandlerB(),
new OutboundHandlerC(),
new ExceptionCaughtHandler()
);
}
}
class ExceptionCaughtHandler extends ChannelInboundHandlerAdapter{
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (cause instanceof BusinessException) {
System.out.println("is BusinessException");
}
}
}