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