欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

关于Netty的BlockingOperationException问题

程序员文章站 2022-04-22 18:03:37
...

记录遇到的一个死锁异常。出现的场景是在客户端断线重连时。

重连时代码类似这样:

	private void reconnect() {
		if (bootstrap == null)
			throw new IllegalArgumentException("bootstrap cannot be null");
		//如果已经连接,则直接【连接成功】
		if (channel == null || !channel.isActive()) {
			//连接
            channel = bootstrap.connect().awaitUninterruptibly().channel();
		}
	}

抛出异常是这样:

io.netty.util.concurrent.BlockingOperationException: DefaultChannelPromise@40edf502(incomplete)
	at io.netty.util.concurrent.DefaultPromise.checkDeadLock(DefaultPromise.java:386)
	at io.netty.channel.DefaultChannelPromise.checkDeadLock(DefaultChannelPromise.java:159)
	at io.netty.util.concurrent.DefaultPromise.awaitUninterruptibly(DefaultPromise.java:236)
	at io.netty.channel.DefaultChannelPromise.awaitUninterruptibly(DefaultChannelPromise.java:137)
	at io.netty.channel.DefaultChannelPromise.awaitUninterruptibly(DefaultChannelPromise.java:30)

自然就去看源码:

//DefaultPromise.java
    public Promise<V> awaitUninterruptibly() {
        if (this.isDone()) {
            return this;
        } else {
            this.checkDeadLock();
            boolean interrupted = false;
            synchronized(this) {
                while(!this.isDone()) {
                    this.incWaiters();

                    try {
                        this.wait();
                    } catch (InterruptedException var9) {
                        interrupted = true;
                    } finally {
                        this.decWaiters();
                    }
                }
            }

            if (interrupted) {
                Thread.currentThread().interrupt();
            }

            return this;
        }
    }


//DefaultPromise.java
    protected void checkDeadLock() {
        EventExecutor e = this.executor();
        if (e != null && e.inEventLoop()) {
            throw new BlockingOperationException(this.toString());
        }
    }

//AbstractEventExecutor.java
    public boolean inEventLoop() {
        return this.inEventLoop(Thread.currentThread());
    }

//SingleThreadEventExecutor.java
    public boolean inEventLoop(Thread thread) {
        return thread == this.thread;
    }

发现这是在判断当前线程(抛出异常的线程)是否就是运行bootstrap的这条线程,如果是,那么就抛出异常。

同一条线程自然不能同时await与notify,所以抛出异常很正常。

调试了一会,发现只有在比较老旧的机子上运行才会出现这种问题…于是怀疑方向转向性能,或者说转向线程数量。

    bootstrap.group(new NioEventLoopGroup())
//MultithreadEventLoopGroup.java
    private static final int DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));

看到这里,我使用了

    bootstrap.group(new NioEventLoopGroup(1))

发现重连会百分百死锁,于是这个锅就得扣到硬件的性能头上了。

起因是await操作,任何await操作都有BlockingOperationException风险,比如一些大佬所说的只使用ctx.write而不flush之类的问题,其实内部也是因为await。然后才是线程数量,如果可能,尽量手动给定线程数。