epoll bugjava
selector.select()
操做是阻塞的,只有被监听的fd有读写操做时,才被唤醒select()
操做依旧被唤醒selectedKeys()
返回的是个空数组while(true)
处,循环执行,致使死循环。JDK bug列表中有两个相关的bug报告:数组
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会被唤醒。app
这是与操做系统机制有关系的,JDK虽然仅仅是一个兼容各个操做系统平台的软件,但很遗憾在JDK5和JDK6最初的版本中(严格意义上来将,JDK部分版本都是),这个问题并无解决,而将这个帽子抛给了操做系统方,这也就是这个bug最终一直到2013年才最终修复的缘由,最终影响力太广。jvm
grizzly的commiteer们最早进行修改的,而且经过众多的测试说明这种修改方式大大下降了JDK NIO的问题。socket
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(); }
可是,这种修改仍然不是可靠的,一共有两点:测试
最终的终极办法是建立一个新的Selector:ui
Trash wasted Selector, creates a new one.this
Jetty首先定义两了-D参数:
org.mortbay.io.nio.JVMBUG_THRESHHOLD, defaults to 512 and is the number of zero select returns that must be exceeded in a period.
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次数)Jetty解决空轮询bug
思路和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