前言
使用netty作为nio框架开发遇到了一个问题,断线重连时添加自定义channel handler失败,抛出异常
ChannelPiplineException:"xxx class is not a @Sharable handler, so can't be added or removed multiple times
字面意思上是我们自定义的channel handler不是一个共享的handler,不能被重复添加多次。那什么是可共享的handler呢?为什么pipline不允许添加不可共享的handler?解决问题的最好方式就是阅读源码。
可共享的handler
首先找到异常抛出的地点,ChannelPipline类中的addLast,一系列私有方法的调用(涉及其他的检查与处理但我们这里不需要关心)最终调用到addLast0这个方法:
private void addLast0(final String name, DefaultChannelHandlerContext newCtx) {
checkMultiplicity(newCtx);
DefaultChannelHandlerContext prev = tail.prev;
newCtx.prev = prev;
newCtx.next = tail;
prev.next = newCtx;
tail.prev = newCtx;
name2ctx.put(name, newCtx);
callHandlerAdded(newCtx);
}
可以看到方法开头就是对handler重复添加的检查,点进去看一下:
private static void checkMultiplicity(ChannelHandlerContext ctx) {
ChannelHandler handler = ctx.handler();
if (handler instanceof ChannelHandlerAdapter) {
ChannelHandlerAdapter h = (ChannelHandlerAdapter) handler;
if (!h.isSharable() && h.added) {
throw new ChannelPipelineException(
h.getClass().getName() +
" is not a @Sharable handler, so can't be added or removed multiple times.");
}
h.added = true;
}
}
果然在这个方法里检查了handler是否是可共享的,判断逻辑就是看这个handler有没有被@Sharable这个接口所注解
public boolean isSharable() {
return getClass().isAnnotationPresent(Sharable.class);
}
@Sharable注解
@Inherited
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Sharable {
// no value
}
注意@Inherited这个元注解,这样被@Sharable注解的抽象handler的具体实现类也具备被共享的能力啦。
为什么要设置可共享检查
了解了什么是可共享handler和怎么设置可共享handler,接下来的问题是为什么框架作者要煞费苦心的做这么一层检查呢?如果滥用@Sharable接口又会有哪些问题?这首先得从netty的线程模型说起。
netty中负责通信的channel始终只与一个event loop(可以抽象的看为一个处理线程)绑定,所以在handler非共享的情况下,同一个channel上的通信永远是线程安全的。这种线程模型让我们不用考虑多线程带来的安全隐患极大的减少了开发难度,当多个channel共享同一个handler时,如果handler持有的竞态资源如果没有考虑线程安全,很可能带来许多严重且难以排查的问题,因此我们在开发时应该遵循netty框架设计者的思路,尽量减少可共享handler的使用,如果必须使用,也必须在保证线程安全的情况下进行handler的共享。