JDK Epoll空轮询bug

作者: 齐晋 | 来源:发表于2018-01-06 16:11 被阅读426次

本文主要在应用服务器中对JDK的epoll空转bug的处理基础上做了修补。

bug表现

epoll bug
  • 正常情况下,selector.select()操作是阻塞的,只有被监听的fd有读写操作时,才被唤醒
  • 但是,在这个bug中,没有任何fd有读写请求,但是select()操作依旧被唤醒
  • 很显然,这种情况下,selectedKeys()返回的是个空数组
  • 然后按照逻辑执行到while(true)处,循环执行,导致死循环。

bug原因

JDK bug列表中有两个相关的bug报告:

  1. JDK-6670302 : (se) NIO selector wakes up with 0 selected keys infinitely
  2. JDK-6403933 : (se) Selector doesn't block on Selector.select(timeout) (lnx)

JDK-6403933的bug说出了实质的原因:

This is an issue with poll (and epoll) on Linux. If a file descriptor for a connected socket is polled with a request event mask of 0, and if the connection is abruptly terminated (RST) then the poll wakes up with the POLLHUP (and maybe POLLERR) bit set in the returned event set. The implication of this behaviour is that Selector will wakeup and as the interest set for the SocketChannel is 0 it means there aren't any selected events and the select method returns 0.

具体解释为:在部分Linux的2.6的kernel中,poll和epoll对于突然中断的连接socket会对返回的eventSet事件集合置为POLLHUP,也可能是POLLERR,eventSet事件集合发生了变化,这就可能导致Selector会被唤醒。

这是与操作系统机制有关系的,JDK虽然仅仅是一个兼容各个操作系统平台的软件,但很遗憾在JDK5和JDK6最初的版本中(严格意义上来将,JDK部分版本都是),这个问题并没有解决,而将这个帽子抛给了操作系统方,这也就是这个bug最终一直到2013年才最终修复的原因,最终影响力太广。

解决办法

不完善的解决办法

grizzly的commiteer们最先进行修改的,并且通过众多的测试说明这种修改方式大大降低了JDK NIO的问题。

if (SelectionKey != null)  {  // the key you registered on the temporary selector
   SelectionKey.cancel();   // cancel the SelectionKey that was registered with the temporary selector
   // flush the cancelled key
   temporarySelector.selectNow();
} 

但是,这种修改仍然不是可靠的,一共有两点:

  1. 多个线程中的SelectionKey的key的cancel,很可能和下面的Selector.selectNow同时并发,如果是导致key的cancel后运行很可能没有效果
  2. 与其说第一点使得NIO空转出现的几率大大降低,经过Jetty服务器的测试报告发现,这种重复利用Selector并清空SelectionKey的改法很可能没有任何的效果,

完善的解决办法

最终的终极办法是创建一个新的Selector:

Trash wasted Selector, creates a new one.

各应用具体解决方法

Jetty

Jetty首先定义两了-D参数:

  • JVMBUG_THRESHHOLD

org.mortbay.io.nio.JVMBUG_THRESHHOLD, defaults to 512 and is the number of zero select returns that must be exceeded in a period.

  • threshhold

org.mortbay.io.nio.MONITOR_PERIOD defaults to 1000 and is the period over which the threshhold applies.

第一个参数是select返回值为0的计数,第二个是多长时间,整体意思就是控制在多长时间内,如果Selector.select不断返回0,说明进入了JVM的bug的模式。

做法是:

  • 记录select()返回为0的次数(记做jvmBug次数)
  • 在MONITOR_PERIOD时间范围内,如果jvmBug次数超过JVMBUG_THRESHHOLD,则新创建一个selector
Jetty解决空轮询bug

Netty

思路和Jetty的处理方式几乎是一样的,就是netty讲重建Selector的过程抽取成了一个方法。

long currentTimeNanos = System.nanoTime();
for (;;) {
    // 1.定时任务截止事时间快到了,中断本次轮询
    ...
    // 2.轮询过程中发现有任务加入,中断本次轮询
    ...
    // 3.阻塞式select操作
    selector.select(timeoutMillis);
    // 4.解决jdk的nio bug
    long time = System.nanoTime();
    if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
        selectCnt = 1;
    } else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
            selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {

        rebuildSelector();
        selector = this.selector;
        selector.selectNow();
        selectCnt = 1;
        break;
    }
    currentTimeNanos = time; 
    ...
 }

netty 会在每次进行 selector.select(timeoutMillis) 之前记录一下开始时间currentTimeNanos,在select之后记录一下结束时间,判断select操作是否至少持续了timeoutMillis秒(这里将time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos改成time - currentTimeNanos >= TimeUnit.MILLISECONDS.toNanos(timeoutMillis)或许更好理解一些),
如果持续的时间大于等于timeoutMillis,说明就是一次有效的轮询,重置selectCnt标志,否则,表明该阻塞方法并没有阻塞这么长时间,可能触发了jdk的空轮询bug,当空轮询的次数超过一个阀值的时候,默认是512,就开始重建selector


参考

相关文章

  • NIO的epoll空轮询bug

    NIO的epoll空轮询bug JDK NIO的BUG,例如臭名昭著的epoll bug,它会导致Selector...

  • NIO中存在的bug—epoll空轮询

    IO&NIO介绍 NIO中epoll空轮询表现 bug原因 JDK bug列表中有两个相关的bug报告: JDK-...

  • JDK Epoll空轮询bug

    本文主要在应用服务器中对JDK的epoll空转bug的处理基础上做了修补。 bug表现 正常情况下,selecto...

  • Netty——解决Selector 空轮询BUG

    一、前言 JDK NIO的BUG,例如臭名昭著的epoll bug,它会导致Selector空轮询,最终导致CPU...

  • epoll bug fix in netty

    epoll bug简述 在NIO中通过Selector的轮询当前是否有IO事件就绪,根据JDK NIO api描述...

  • netty系列之(三)——EventLoop和EventLoop

    一、线程模型 Q 1、默认情况下netty服务端起多少个线程?何时启动?2、netty如何解决jdk空轮询的bug...

  • epoll 事件模型详解

    epoll 主要采用对已就绪的 fd 进行轮询操作 一、epoll 触发方式 epoll支持 ET 和 LT 两种...

  • netty解决空轮询bug

    selector在没有结果的情况下,依然被唤醒,导致一直空轮询,cpu100%直接定位到NioEventLoop ...

  • JDK 的 3 个 bug

    JDK 的 3 个 bug 1.Annotation引用非空enum数组返回空数组 首次发现时的环境:JDK 1....

  • jdk中的nio

    jdk中的nioJava NIO底层也是基于epoll调用,见jdk中的EpollSelectorImpl htt...

网友评论

    本文标题:JDK Epoll空轮询bug

    本文链接:https://www.haomeiwen.com/subject/otvynxtx.html