Java 选择器.select(超时)在超时之前返回0

Java 选择器.select(超时)在超时之前返回0,java,nio,Java,Nio,据Javadoc说, 它仅在至少选择一个通道、调用此选择器的唤醒方法、中断当前线程或给定的超时期限到期(以先到者为准)后返回 但有时它会在没有以下4种情况的情况下返回: 至少选择了一个通道:它返回0 已调用wakeup方法:未调用wakeup 当前线程被中断:thread.interrupted()返回false 给定超时期限到期:根据日志未到期 更新日期2016-03-15 在第392行和第402行的源代码中,我添加了一些日志: 这很奇怪:没有选择的键,没有中断,没有超时,也没有唤醒,但它返

据Javadoc说,

它仅在至少选择一个通道、调用此选择器的唤醒方法、中断当前线程或给定的超时期限到期(以先到者为准)后返回

但有时它会在没有以下4种情况的情况下返回:

  • 至少选择了一个通道:它返回0
  • 已调用wakeup方法:
    未调用wakeup
  • 当前线程被中断:thread.interrupted()返回false
  • 给定超时期限到期:根据日志未到期
  • 更新日期2016-03-15

    在第392行和第402行的源代码中,我添加了一些日志:

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

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

    这一点非常清楚

    在每个选择操作期间,可以向选择器的选定关键点集添加关键点,也可以从中删除关键点。选择由select()、select(long)和selectNow()方法执行,包括三个步骤:

  • 查询底层操作系统,以更新每个剩余通道在选择操作开始时是否准备好执行由其密钥兴趣集标识的任何操作。对于准备好进行至少一次此类操作的通道,将执行以下两个操作之一:

  • 如果通道的密钥不在所选密钥集中,则会将其添加到该密钥集中,并修改其就绪操作集,以准确标识通道现在报告为就绪的操作。先前记录在就绪集中的任何就绪信息都将被丢弃

  • 否则,通道的密钥已在所选密钥集中,因此其就绪操作集将被修改,以标识报告通道已就绪的任何新操作。保留之前记录在就绪集合中的任何就绪信息;换句话说,底层系统返回的就绪集按位分离到键的当前就绪集

  • 发生的情况是,在返回零的select上,select键已经在selected键集中,因此就绪键的数量没有发生变化

    另请注意
    select(int timeout)
    方法部分(我的重点):

    返回:

    • 已更新其就绪操作集的键数,可能为零

    这可能真的是JDK中的一个bug。Netty和Mina似乎也遇到了这样的问题,他们将选择器重建为一种解决方法

    见最新净代码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代码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();
                        }
                    }
    
    if(!wakeupCalled.getAndSet(false)&&(selected==0)&(delta<100)){
    //最后一次机会:select()可能已被删除
    //因为我们有一个封闭的通道而中断。
    如果(isBrokenConnection()){
    日志警告(“断开的连接”);
    }否则{
    LOG.warn(“创建一个新选择器。所选为0,delta=“+(t1-t0));
    //好吧,我们被可怕的厄波尔击中了
    //旋转。
    //基本上,有一个种族条件
    //这会导致关闭文件描述符不可用
    //视为可用的选定频道,
    //但是
    //它停止了选择。下次我们将
    //调用select(),它将立即退出
    //同样的
    //理性,永远如此,消耗100%
    //中央处理器。
    //我们必须摧毁选择器,然后
    //将所有套接字注册到新的套接字上。
    registerNewSelector();
    }
    }
    

    因此,如果select()返回意外的零,则注册新选择器可能是最佳做法。

    在我的程序中,一旦选中,所有SelectedKey都将被处理和清除。所以我认为next select不应该返回零,除非超时。检查这一点的方法是,当你得到一个意外的零返回时,迭代所有的就绪键,检查就绪操作,看看有什么。你怎么确定你已经处理了所有的就绪操作?当我得到意外的零时,没有就绪键。我使用了selector.selectedKeys(),但它似乎是一个空集:-(这可能真的是JDK中的一个bug。Netty和Mina似乎也遇到了这样的问题,他们将重建选择器作为解决方法。我遇到了这种行为(select()不断返回零)在android中,发现这是由于使用
    interestTops==0
    注册频道引起的,例如
    channel.register(选择器,0)
    Nice well writed question+1您是否从此进程启动外部进程?@RoeeShenberg否。只有一个java进程而不调用其他进程。我最近遇到这样一个错误,原因是一个打开的文件,其中已注册的特定描述符已经关闭(例如,文件描述符被复制)(在我的例子中,这是由fork()引起的))。linux epoll接口以这种方式被破坏(尽管用于注册epoll的描述符已关闭,但该文件仍保持为epoll注册)。要进行调试,可以执行的操作是清除有问题的进程(筛选
    epoll\u wait
    ),然后查看返回的文件描述符,最后使用
    lsof
    了解更多信息(例如,如果是套接字,目标是什么)
                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;
                }
    
                    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();
                        }
                    }