java - Selector.select(timeout) 在超时前返回 0

标签 java nio

根据 Javadoc,

It returns only after at least one channel is selected, this selector's wakeup method is invoked, the current thread is interrupted, or the given timeout period expires, whichever comes first.

但偶尔它会在没有这 4 种情况的情况下返回:

  1. 至少选择了一个 channel :返回0
  2. 唤醒方法被调用:wakeup 没有被调用
  3. 当前线程被中断:Thread.interrupted() 返回 false
  4. given timeout period expires:根据日志未过期

2016 年 3 月 15 日更新

在第 392 行和第 402 行的源代码中,我添加了一些日志: https://github.com/xqbase/tuna/blob/debug/core/src/main/java/com/xqbase/tuna/ConnectorImpl.java

public boolean doEvents(long timeout) {
    Log.v("Before Select: " + timeout);
    int keySize;
    try {
        keySize = timeout == 0 ? selector.selectNow() :
                timeout < 0 ? selector.select() : selector.select(timeout);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
    Set<SelectionKey> selectedKeys = selector.selectedKeys();
    if (keySize == 0) {
        Log.v("After Select(0): selectedKeys=" + selectedKeys.size() + ", " +
                "interrupt=" + Thread.interrupted());
        invokeQueue();
        return false;
    }

    for (SelectionKey key : selectedKeys) {
        ...

这是日志:

...
2016-03-15 23:07:49.695 com.xqbase.tuna.ConnectorImpl doEvents
FINE: Before Select: 8120
2016-03-15 23:07:49.696 com.xqbase.tuna.ConnectorImpl doEvents
FINE: After Select(0): selectedKeys=0, interrupt=false
2016-03-15 23:07:49.696 com.xqbase.tuna.ConnectorImpl doEvents
FINE: Before Select: 8119
2016-03-15 23:07:49.696 com.xqbase.tuna.ConnectorImpl doEvents
FINE: After Select(0): selectedKeys=0, interrupt=false
2016-03-15 23:07:49.700 com.xqbase.tuna.ConnectorImpl doEvents
FINE: Before Select: 8115
2016-03-15 23:07:49.701 com.xqbase.tuna.ConnectorImpl doEvents
FINE: After Select(0): selectedKeys=0, interrupt=false
2016-03-15 23:07:49.701 com.xqbase.tuna.ConnectorImpl doEvents
FINE: Before Select: 8114
2016-03-15 23:07:49.702 com.xqbase.tuna.ConnectorImpl doEvents
FINE: After Select(0): selectedKeys=0, interrupt=false
...

很奇怪:没有选中键,没有中断,没有超时,也没有唤醒,但是它返回了。

Java 中有错误吗?我的 Java 版本是 1.8.0_51-b16(64 位服务器虚拟机),在 CentOS 6.5 x64 linode 上运行。

最佳答案

这可能真的是 JDK 中的一个错误。 Netty 和 Mina 好像也遇到了这样的问题,他们重建选择器作为解决方法。

查看最新的 Netty 代码 https://github.com/netty/netty/blob/4.1/transport/src/main/java/io/netty/channel/nio/NioEventLoop.java L641-681:

            if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
                // - Selected something,
                // - waken up by user, or
                // - the task queue has a pending task.
                // - a scheduled task is ready for processing
                break;
            }
            ...
            } else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
                    selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
                // The selector returned prematurely many times in a row.
                // Rebuild the selector to work around the problem.
                logger.warn(
                        "Selector.select() returned prematurely {} times in a row; rebuilding selector.",
                        selectCnt);

                rebuildSelector();
                selector = this.selector;

                // Select again to populate selectedKeys.
                selector.selectNow();
                selectCnt = 1;
                break;
            }

参见 Mina 2.0 代码 https://github.com/apache/mina/blob/2.0/mina-core/src/main/java/org/apache/mina/core/polling/AbstractPollingIoProcessor.java L1070-1092:

                if (!wakeupCalled.getAndSet(false) && (selected == 0) && (delta < 100)) {
                    // Last chance : the select() may have been
                    // interrupted because we have had an closed channel.
                    if (isBrokenConnection()) {
                        LOG.warn("Broken connection");
                    } else {
                        LOG.warn("Create a new selector. Selected is 0, delta = " + (t1 - t0));
                        // Ok, we are hit by the nasty epoll
                        // spinning.
                        // Basically, there is a race condition
                        // which causes a closing file descriptor not to be
                        // considered as available as a selected channel,
                        // but
                        // it stopped the select. The next time we will
                        // call select(), it will exit immediately for the
                        // same
                        // reason, and do so forever, consuming 100%
                        // CPU.
                        // We have to destroy the selector, and
                        // register all the socket on a new one.
                        registerNewSelector();
                    }
                }

因此,如果 select() 返回意外的,注册一个新的选择器可能是最佳实践。

关于java - Selector.select(timeout) 在超时前返回 0,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35858537/

相关文章:

java - 大O符号作业--代码片段算法分析?

java - 是否可以在单个端口上运行两个 NIO 服务器套接字?

Java根据文件名中的日期查找目录中的最新文件

java - 将 LongBuffer/IntBuffer/ShortBuffer 转换为 ByteBuffer

java - 创建这种 Java2D 外观所需的技术

java - BooleanFieldEditor 不从 PreferenceStore 加载值

java - 在 PL/SQL 中重写 java 代码的挑战

JavaFX:将 TextProperty(例如标签)绑定(bind)到一个简单的整数

java - 何时在 JAVA 的 glob 语法中使用 **(双星)

Java:文件重命名检测