正确使用sharable注解还是必要的

释义

在使用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;
            }
            ....省略
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值