释义
在使用netty编写程序的时候,不可避免的会使用到sharable注解,netty对它的注释也是比较简单
Indicates that the same instance of the annotated ChannelHandler can be added to one or more ChannelPipelines multiple times without a race condition.
被注解的ChannelHandler对应的同一个实例可以被加入到一个或者多个ChannelPipelines一次或者多次,而不会存在竞争条件。
sharable注解定义在ChannelHandler接口里面,该注解被使用是在ChannelHandlerAdapter类里面,被sharable注解标记过的实例都会存入当前加载线程的threadlocal里面,如下所示
public boolean isSharable() {
/**
* Cache the result of {@link Sharable} annotation detection to workaround a condition. We use a
* {@link ThreadLocal} and {@link WeakHashMap} to eliminate the volatile write/reads. Using different
* {@link WeakHashMap} instances per {@link Thread} is good enough for us and the number of
* {@link Thread}s are quite limited anyway.
*
* See <a href="https://github.com/netty/netty/issues/2289">#2289</a>.
*/
Class<?> clazz = getClass();
Map<Class<?>, Boolean> cache = InternalThreadLocalMap.get().handlerSharableCache();
Boolean sharable = cache.get(clazz);
if (sharable == null) {
sharable = clazz.isAnnotationPresent(Sharable.class);
cache.put(clazz, sharable);
}
return sharable;
}
netty客户端重连时遇到的问题
在写一个netty客户端时,对应的handler没有增加sharable注解,当执行重连的逻辑时,此时会收到一个莫名其妙的错误java.nio.channels.ClosedChannelException,客户端每次connect都会重新建立一个channel,为什么会说是关掉的channel呢?代码如下,其中ClientHandler没有使用sharable注解
private static void connect() throws Exception {
final Bootstrap b = BootStrapManager.getBootStrap();
b.handler(new ClientInitializer(new ClientHandler()));
b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, CONNECT_TIMEOUT);
ChannelFuture f = b.connect(server, port);
f.addListener(new ReconnectListener(b));
}
static class ReconnectListener implements ChannelFutureListener{
private Bootstrap b;
public ReconnectListener(Bootstrap b) {
this.b = b;
}
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if(!future.isSuccess()) {
future = b.connect(server, port);
if(times-- > 0) {
future.addListener(this);
}
}
if(future.isSuccess()) {
future.channel().closeFuture().get(MAX_WAIT_TIME, TimeUnit.SECONDS);
}
}
}
原来,在b.connect(server, port)的时候,会调用到AbstractBootstrap类的initAndRegister方法,经过channel初始化后,继而会调用到AbstractUnsafe对应的register0方法,最后会调用到ChannelInitializer对应的channelRegistered方法代码如下:
public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {
ChannelPipeline pipeline = ctx.pipeline();
boolean success = false;
try {
initChannel((C) ctx.channel());
pipeline.remove(this);
ctx.fireChannelRegistered();
success = true;
} catch (Throwable t) {
logger.warn("Failed to initialize a channel. Closing: " + ctx.channel(), t);
} finally {
if (pipeline.context(this) != null) {
pipeline.remove(this);
}
if (!success) {
ctx.close();
}
}
}
原因就在这段代码initChannel((C) ctx.channel())中,initChannel方法其实就是调用的传入的ClientInitializer,我自定义的实现,实现也很简单只是把自己实现的ClientHandler传入进来,调用ch.pipeline().addLast(handler),关键就是再执行addLast的时候,它会做了一次校验:
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;
}
}
可以看到,对于一个非sharable注解的handler,在第一次connect的时候isSharable()方法返回false,但是h.added是返回false的,可以通过,但是当第二次被加入的时候,就出问题了isSharable()方法还是返回false,h.added也是true了,所以会抛出ChannelPipelineException,ChannelInitializer对应的channelRegistered方法捕捉到此异常后,没有执行success = true,导致ctx.close()方法执行,进而在connect的时候,ensureOpen方法将promise设置为CLOSED_CHANNEL_EXCEPTION,返回了,导致连接失败
public final void connect(
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
....省略