View Javadoc
1   /*
2    * Copyright 2024 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.channel.nio;
17  
18  import io.netty.channel.ChannelException;
19  import io.netty.channel.DefaultSelectStrategyFactory;
20  import io.netty.channel.IoHandlerContext;
21  import io.netty.channel.IoHandle;
22  import io.netty.channel.IoHandler;
23  import io.netty.channel.IoHandlerFactory;
24  import io.netty.channel.IoOps;
25  import io.netty.channel.IoRegistration;
26  import io.netty.channel.SelectStrategy;
27  import io.netty.channel.SelectStrategyFactory;
28  import io.netty.util.IntSupplier;
29  import io.netty.util.concurrent.ThreadAwareExecutor;
30  import io.netty.util.internal.ObjectUtil;
31  import io.netty.util.internal.PlatformDependent;
32  import io.netty.util.internal.ReflectionUtil;
33  import io.netty.util.internal.StringUtil;
34  import io.netty.util.internal.SystemPropertyUtil;
35  import io.netty.util.internal.logging.InternalLogger;
36  import io.netty.util.internal.logging.InternalLoggerFactory;
37  
38  import java.io.IOException;
39  import java.lang.reflect.Field;
40  import java.nio.channels.CancelledKeyException;
41  import java.nio.channels.Selector;
42  import java.nio.channels.SelectionKey;
43  
44  import java.nio.channels.spi.SelectorProvider;
45  import java.security.AccessController;
46  import java.security.PrivilegedAction;
47  import java.util.ArrayList;
48  import java.util.Collection;
49  import java.util.Iterator;
50  import java.util.Set;
51  import java.util.concurrent.TimeUnit;
52  import java.util.concurrent.atomic.AtomicBoolean;
53  
54  /**
55   * {@link IoHandler} implementation which register the {@link IoHandle}'s to a {@link Selector}.
56   */
57  public final class NioIoHandler implements IoHandler {
58  
59      private static final InternalLogger logger = InternalLoggerFactory.getInstance(NioIoHandler.class);
60  
61      private static final int CLEANUP_INTERVAL = 256; // XXX Hard-coded value, but won't need customization.
62  
63      private static final boolean DISABLE_KEY_SET_OPTIMIZATION =
64              SystemPropertyUtil.getBoolean("io.netty.noKeySetOptimization", false);
65  
66      private static final int MIN_PREMATURE_SELECTOR_RETURNS = 3;
67      private static final int SELECTOR_AUTO_REBUILD_THRESHOLD;
68  
69      private final IntSupplier selectNowSupplier = new IntSupplier() {
70          @Override
71          public int get() throws Exception {
72              return selectNow();
73          }
74      };
75  
76      // Workaround for JDK NIO bug.
77      //
78      // See:
79      // - https://bugs.openjdk.java.net/browse/JDK-6427854 for first few dev (unreleased) builds of JDK 7
80      // - https://bugs.openjdk.java.net/browse/JDK-6527572 for JDK prior to 5.0u15-rev and 6u10
81      // - https://github.com/netty/netty/issues/203
82      static {
83          int selectorAutoRebuildThreshold = SystemPropertyUtil.getInt("io.netty.selectorAutoRebuildThreshold", 512);
84          if (selectorAutoRebuildThreshold < MIN_PREMATURE_SELECTOR_RETURNS) {
85              selectorAutoRebuildThreshold = 0;
86          }
87  
88          SELECTOR_AUTO_REBUILD_THRESHOLD = selectorAutoRebuildThreshold;
89  
90          if (logger.isDebugEnabled()) {
91              logger.debug("-Dio.netty.noKeySetOptimization: {}", DISABLE_KEY_SET_OPTIMIZATION);
92              logger.debug("-Dio.netty.selectorAutoRebuildThreshold: {}", SELECTOR_AUTO_REBUILD_THRESHOLD);
93          }
94      }
95  
96      /**
97       * The NIO {@link Selector}.
98       */
99      private Selector selector;
100     private Selector unwrappedSelector;
101     private SelectedSelectionKeySet selectedKeys;
102 
103     private final SelectorProvider provider;
104 
105     /**
106      * Boolean that controls determines if a blocked Selector.select should
107      * break out of its selection process. In our case we use a timeout for
108      * the select method and the select method will block for that time unless
109      * waken up.
110      */
111     private final AtomicBoolean wakenUp = new AtomicBoolean();
112 
113     private final SelectStrategy selectStrategy;
114     private final ThreadAwareExecutor executor;
115     private int cancelledKeys;
116     private boolean needsToSelectAgain;
117 
118     private NioIoHandler(ThreadAwareExecutor executor, SelectorProvider selectorProvider,
119                          SelectStrategy strategy) {
120         this.executor = ObjectUtil.checkNotNull(executor, "executionContext");
121         this.provider = ObjectUtil.checkNotNull(selectorProvider, "selectorProvider");
122         this.selectStrategy = ObjectUtil.checkNotNull(strategy, "selectStrategy");
123         final SelectorTuple selectorTuple = openSelector();
124         this.selector = selectorTuple.selector;
125         this.unwrappedSelector = selectorTuple.unwrappedSelector;
126     }
127 
128     private static final class SelectorTuple {
129         final Selector unwrappedSelector;
130         final Selector selector;
131 
132         SelectorTuple(Selector unwrappedSelector) {
133             this.unwrappedSelector = unwrappedSelector;
134             this.selector = unwrappedSelector;
135         }
136 
137         SelectorTuple(Selector unwrappedSelector, Selector selector) {
138             this.unwrappedSelector = unwrappedSelector;
139             this.selector = selector;
140         }
141     }
142 
143     private SelectorTuple openSelector() {
144         final Selector unwrappedSelector;
145         try {
146             unwrappedSelector = provider.openSelector();
147         } catch (IOException e) {
148             throw new ChannelException("failed to open a new selector", e);
149         }
150 
151         if (DISABLE_KEY_SET_OPTIMIZATION) {
152             return new SelectorTuple(unwrappedSelector);
153         }
154 
155         Object maybeSelectorImplClass = AccessController.doPrivileged(new PrivilegedAction<Object>() {
156             @Override
157             public Object run() {
158                 try {
159                     return Class.forName(
160                             "sun.nio.ch.SelectorImpl",
161                             false,
162                             PlatformDependent.getSystemClassLoader());
163                 } catch (Throwable cause) {
164                     return cause;
165                 }
166             }
167         });
168 
169         if (!(maybeSelectorImplClass instanceof Class) ||
170                 // ensure the current selector implementation is what we can instrument.
171                 !((Class<?>) maybeSelectorImplClass).isAssignableFrom(unwrappedSelector.getClass())) {
172             if (maybeSelectorImplClass instanceof Throwable) {
173                 Throwable t = (Throwable) maybeSelectorImplClass;
174                 logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, t);
175             }
176             return new SelectorTuple(unwrappedSelector);
177         }
178 
179         final Class<?> selectorImplClass = (Class<?>) maybeSelectorImplClass;
180         final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();
181 
182         Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
183             @Override
184             public Object run() {
185                 try {
186                     Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
187                     Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");
188 
189                     if (PlatformDependent.javaVersion() >= 9 && PlatformDependent.hasUnsafe()) {
190                         // Let us try to use sun.misc.Unsafe to replace the SelectionKeySet.
191                         // This allows us to also do this in Java9+ without any extra flags.
192                         long selectedKeysFieldOffset = PlatformDependent.objectFieldOffset(selectedKeysField);
193                         long publicSelectedKeysFieldOffset =
194                                 PlatformDependent.objectFieldOffset(publicSelectedKeysField);
195 
196                         if (selectedKeysFieldOffset != -1 && publicSelectedKeysFieldOffset != -1) {
197                             PlatformDependent.putObject(
198                                     unwrappedSelector, selectedKeysFieldOffset, selectedKeySet);
199                             PlatformDependent.putObject(
200                                     unwrappedSelector, publicSelectedKeysFieldOffset, selectedKeySet);
201                             return null;
202                         }
203                         // We could not retrieve the offset, lets try reflection as last-resort.
204                     }
205 
206                     Throwable cause = ReflectionUtil.trySetAccessible(selectedKeysField, true);
207                     if (cause != null) {
208                         return cause;
209                     }
210                     cause = ReflectionUtil.trySetAccessible(publicSelectedKeysField, true);
211                     if (cause != null) {
212                         return cause;
213                     }
214 
215                     selectedKeysField.set(unwrappedSelector, selectedKeySet);
216                     publicSelectedKeysField.set(unwrappedSelector, selectedKeySet);
217                     return null;
218                 } catch (NoSuchFieldException | IllegalAccessException e) {
219                     return e;
220                 }
221             }
222         });
223 
224         if (maybeException instanceof Exception) {
225             selectedKeys = null;
226             Exception e = (Exception) maybeException;
227             logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, e);
228             return new SelectorTuple(unwrappedSelector);
229         }
230         selectedKeys = selectedKeySet;
231         logger.trace("instrumented a special java.util.Set into: {}", unwrappedSelector);
232         return new SelectorTuple(unwrappedSelector,
233                 new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet));
234     }
235 
236     /**
237      * Returns the {@link SelectorProvider} used by this {@link NioEventLoop} to obtain the {@link Selector}.
238      */
239     public SelectorProvider selectorProvider() {
240         return provider;
241     }
242 
243     Selector selector() {
244         return selector;
245     }
246 
247     int numRegistered() {
248         return selector().keys().size() - cancelledKeys;
249     }
250 
251     Set<SelectionKey> registeredSet() {
252         return selector().keys();
253     }
254 
255     void rebuildSelector0() {
256         final Selector oldSelector = selector;
257         final SelectorTuple newSelectorTuple;
258 
259         if (oldSelector == null) {
260             return;
261         }
262 
263         try {
264             newSelectorTuple = openSelector();
265         } catch (Exception e) {
266             logger.warn("Failed to create a new Selector.", e);
267             return;
268         }
269 
270         // Register all channels to the new Selector.
271         int nChannels = 0;
272         for (SelectionKey key : oldSelector.keys()) {
273             DefaultNioRegistration handle = (DefaultNioRegistration) key.attachment();
274             try {
275                 if (!key.isValid() || key.channel().keyFor(newSelectorTuple.unwrappedSelector) != null) {
276                     continue;
277                 }
278 
279                 handle.register(newSelectorTuple.unwrappedSelector);
280                 nChannels++;
281             } catch (Exception e) {
282                 logger.warn("Failed to re-register a NioHandle to the new Selector.", e);
283                 handle.cancel();
284             }
285         }
286 
287         selector = newSelectorTuple.selector;
288         unwrappedSelector = newSelectorTuple.unwrappedSelector;
289 
290         try {
291             // time to close the old selector as everything else is registered to the new one
292             oldSelector.close();
293         } catch (Throwable t) {
294             if (logger.isWarnEnabled()) {
295                 logger.warn("Failed to close the old Selector.", t);
296             }
297         }
298 
299         if (logger.isInfoEnabled()) {
300             logger.info("Migrated " + nChannels + " channel(s) to the new Selector.");
301         }
302     }
303 
304     private static NioIoHandle nioHandle(IoHandle handle) {
305         if (handle instanceof NioIoHandle) {
306             return (NioIoHandle) handle;
307         }
308         throw new IllegalArgumentException("IoHandle of type " + StringUtil.simpleClassName(handle) + " not supported");
309     }
310 
311     private static NioIoOps cast(IoOps ops) {
312         if (ops instanceof NioIoOps) {
313             return (NioIoOps) ops;
314         }
315         throw new IllegalArgumentException("IoOps of type " + StringUtil.simpleClassName(ops) + " not supported");
316     }
317 
318     final class DefaultNioRegistration implements IoRegistration {
319         private final AtomicBoolean canceled = new AtomicBoolean();
320         private final NioIoHandle handle;
321         private volatile SelectionKey key;
322 
323         DefaultNioRegistration(ThreadAwareExecutor executor, NioIoHandle handle, NioIoOps initialOps, Selector selector)
324                 throws IOException {
325             this.handle = handle;
326             key = handle.selectableChannel().register(selector, initialOps.value, this);
327         }
328 
329         NioIoHandle handle() {
330             return handle;
331         }
332 
333         void register(Selector selector) throws IOException {
334             SelectionKey newKey = handle.selectableChannel().register(selector, key.interestOps(), this);
335             key.cancel();
336             key = newKey;
337         }
338 
339         @SuppressWarnings("unchecked")
340         @Override
341         public <T> T attachment() {
342             return (T) key;
343         }
344 
345         @Override
346         public boolean isValid() {
347             return !canceled.get() && key.isValid();
348         }
349 
350         @Override
351         public long submit(IoOps ops) {
352             if (!isValid()) {
353                 return -1;
354             }
355             int v = cast(ops).value;
356             key.interestOps(v);
357             return v;
358         }
359 
360         @Override
361         public boolean cancel() {
362             if (!canceled.compareAndSet(false, true)) {
363                 return false;
364             }
365             key.cancel();
366             cancelledKeys++;
367             if (cancelledKeys >= CLEANUP_INTERVAL) {
368                 cancelledKeys = 0;
369                 needsToSelectAgain = true;
370             }
371             handle.unregistered();
372             return true;
373         }
374 
375         void close() {
376             cancel();
377             try {
378                 handle.close();
379             } catch (Exception e) {
380                 logger.debug("Exception during closing " + handle, e);
381             }
382         }
383 
384         void handle(int ready) {
385             if (!isValid()) {
386                 return;
387             }
388             handle.handle(this, NioIoOps.eventOf(ready));
389         }
390     }
391 
392     @Override
393     public IoRegistration register(IoHandle handle)
394             throws Exception {
395         NioIoHandle nioHandle = nioHandle(handle);
396         NioIoOps ops = NioIoOps.NONE;
397         boolean selected = false;
398         for (;;) {
399             try {
400                 IoRegistration registration = new DefaultNioRegistration(executor, nioHandle, ops, unwrappedSelector());
401                 handle.registered();
402                 return registration;
403             } catch (CancelledKeyException e) {
404                 if (!selected) {
405                     // Force the Selector to select now as the "canceled" SelectionKey may still be
406                     // cached and not removed because no Select.select(..) operation was called yet.
407                     selectNow();
408                     selected = true;
409                 } else {
410                     // We forced a select operation on the selector before but the SelectionKey is still cached
411                     // for whatever reason. JDK bug ?
412                     throw e;
413                 }
414             }
415         }
416     }
417 
418     @Override
419     public int run(IoHandlerContext context) {
420         int handled = 0;
421         try {
422             try {
423                 switch (selectStrategy.calculateStrategy(selectNowSupplier, !context.canBlock())) {
424                     case SelectStrategy.CONTINUE:
425                         if (context.shouldReportActiveIoTime()) {
426                             context.reportActiveIoTime(0); // Report zero as we did no I/O.
427                         }
428                         return 0;
429 
430                     case SelectStrategy.BUSY_WAIT:
431                         // fall-through to SELECT since the busy-wait is not supported with NIO
432 
433                     case SelectStrategy.SELECT:
434                         select(context, wakenUp.getAndSet(false));
435 
436                         // 'wakenUp.compareAndSet(false, true)' is always evaluated
437                         // before calling 'selector.wakeup()' to reduce the wake-up
438                         // overhead. (Selector.wakeup() is an expensive operation.)
439                         //
440                         // However, there is a race condition in this approach.
441                         // The race condition is triggered when 'wakenUp' is set to
442                         // true too early.
443                         //
444                         // 'wakenUp' is set to true too early if:
445                         // 1) Selector is waken up between 'wakenUp.set(false)' and
446                         //    'selector.select(...)'. (BAD)
447                         // 2) Selector is waken up between 'selector.select(...)' and
448                         //    'if (wakenUp.get()) { ... }'. (OK)
449                         //
450                         // In the first case, 'wakenUp' is set to true and the
451                         // following 'selector.select(...)' will wake up immediately.
452                         // Until 'wakenUp' is set to false again in the next round,
453                         // 'wakenUp.compareAndSet(false, true)' will fail, and therefore
454                         // any attempt to wake up the Selector will fail, too, causing
455                         // the following 'selector.select(...)' call to block
456                         // unnecessarily.
457                         //
458                         // To fix this problem, we wake up the selector again if wakenUp
459                         // is true immediately after selector.select(...).
460                         // It is inefficient in that it wakes up the selector for both
461                         // the first case (BAD - wake-up required) and the second case
462                         // (OK - no wake-up required).
463 
464                         if (wakenUp.get()) {
465                             selector.wakeup();
466                         }
467                         // fall through
468                     default:
469                 }
470             } catch (IOException e) {
471                 // If we receive an IOException here its because the Selector is messed up. Let's rebuild
472                 // the selector and retry. https://github.com/netty/netty/issues/8566
473                 rebuildSelector0();
474                 handleLoopException(e);
475                 return 0;
476             }
477 
478             cancelledKeys = 0;
479             needsToSelectAgain = false;
480 
481             if (context.shouldReportActiveIoTime()) {
482                 // We start the timer after the blocking select() call has returned.
483                 long activeIoStartTimeNanos = System.nanoTime();
484                 handled = processSelectedKeys();
485                 long activeIoEndTimeNanos = System.nanoTime();
486                 context.reportActiveIoTime(activeIoEndTimeNanos - activeIoStartTimeNanos);
487             } else {
488                 handled = processSelectedKeys();
489             }
490         } catch (Error e) {
491             throw e;
492         } catch (Throwable t) {
493             handleLoopException(t);
494         }
495         return handled;
496     }
497 
498     private static void handleLoopException(Throwable t) {
499         logger.warn("Unexpected exception in the selector loop.", t);
500 
501         // Prevent possible consecutive immediate failures that lead to
502         // excessive CPU consumption.
503         try {
504             Thread.sleep(1000);
505         } catch (InterruptedException e) {
506             // Ignore.
507         }
508     }
509 
510     private int processSelectedKeys() {
511         if (selectedKeys != null) {
512             return processSelectedKeysOptimized();
513         } else {
514             return processSelectedKeysPlain(selector.selectedKeys());
515         }
516     }
517 
518     @Override
519     public void destroy() {
520         try {
521             selector.close();
522         } catch (IOException e) {
523             logger.warn("Failed to close a selector.", e);
524         }
525     }
526 
527     private int processSelectedKeysPlain(Set<SelectionKey> selectedKeys) {
528         // check if the set is empty and if so just return to not create garbage by
529         // creating a new Iterator every time even if there is nothing to process.
530         // See https://github.com/netty/netty/issues/597
531         if (selectedKeys.isEmpty()) {
532             return 0;
533         }
534 
535         Iterator<SelectionKey> i = selectedKeys.iterator();
536         int handled = 0;
537         for (;;) {
538             final SelectionKey k = i.next();
539             i.remove();
540 
541             processSelectedKey(k);
542             ++handled;
543 
544             if (!i.hasNext()) {
545                 break;
546             }
547 
548             if (needsToSelectAgain) {
549                 selectAgain();
550                 selectedKeys = selector.selectedKeys();
551 
552                 // Create the iterator again to avoid ConcurrentModificationException
553                 if (selectedKeys.isEmpty()) {
554                     break;
555                 } else {
556                     i = selectedKeys.iterator();
557                 }
558             }
559         }
560         return handled;
561     }
562 
563     private int processSelectedKeysOptimized() {
564         int handled = 0;
565         for (int i = 0; i < selectedKeys.size; ++i) {
566             final SelectionKey k = selectedKeys.keys[i];
567             // null out entry in the array to allow to have it GC'ed once the Channel close
568             // See https://github.com/netty/netty/issues/2363
569             selectedKeys.keys[i] = null;
570 
571             processSelectedKey(k);
572             ++handled;
573 
574             if (needsToSelectAgain) {
575                 // null out entries in the array to allow to have it GC'ed once the Channel close
576                 // See https://github.com/netty/netty/issues/2363
577                 selectedKeys.reset(i + 1);
578 
579                 selectAgain();
580                 i = -1;
581             }
582         }
583         return handled;
584     }
585 
586     private void processSelectedKey(SelectionKey k) {
587         final DefaultNioRegistration registration = (DefaultNioRegistration) k.attachment();
588         if (!registration.isValid()) {
589             try {
590                 registration.handle.close();
591             } catch (Exception e) {
592                 logger.debug("Exception during closing " + registration.handle, e);
593             }
594             return;
595         }
596         registration.handle(k.readyOps());
597     }
598 
599     @Override
600     public void prepareToDestroy() {
601         selectAgain();
602         Set<SelectionKey> keys = selector.keys();
603         Collection<DefaultNioRegistration> registrations = new ArrayList<>(keys.size());
604         for (SelectionKey k: keys) {
605             DefaultNioRegistration handle = (DefaultNioRegistration) k.attachment();
606             registrations.add(handle);
607         }
608 
609         for (DefaultNioRegistration reg: registrations) {
610             reg.close();
611         }
612     }
613 
614     @Override
615     public void wakeup() {
616         if (!executor.isExecutorThread(Thread.currentThread()) && wakenUp.compareAndSet(false, true)) {
617             selector.wakeup();
618         }
619     }
620 
621     @Override
622     public boolean isCompatible(Class<? extends IoHandle> handleType) {
623         return NioIoHandle.class.isAssignableFrom(handleType);
624     }
625 
626     Selector unwrappedSelector() {
627         return unwrappedSelector;
628     }
629 
630     private void select(IoHandlerContext runner, boolean oldWakenUp) throws IOException {
631         Selector selector = this.selector;
632         try {
633             int selectCnt = 0;
634             long currentTimeNanos = System.nanoTime();
635             final long delayNanos = runner.delayNanos(currentTimeNanos);
636             // that's some special value which is used to indicate that no scheduled task is present.
637             // we set the deadline to a bogus (unused) value for us to represent infinity
638             long selectDeadLineNanos = Long.MAX_VALUE;
639             if (delayNanos != Long.MAX_VALUE) {
640                 selectDeadLineNanos = currentTimeNanos + runner.delayNanos(currentTimeNanos);
641             }
642             for (;;) {
643                 final long timeoutMillis;
644                 if (delayNanos != Long.MAX_VALUE) {
645                     long millisBeforeDeadline = millisBeforeDeadline(selectDeadLineNanos, currentTimeNanos);
646                     if (millisBeforeDeadline <= 0) {
647                         if (selectCnt == 0) {
648                             selector.selectNow();
649                             selectCnt = 1;
650                         }
651                         break;
652                     }
653                     timeoutMillis = millisBeforeDeadline;
654                 } else {
655                     // in NIO this means to block without any deadline
656                     timeoutMillis = 0;
657                 }
658                 // If a task was submitted when wakenUp value was true, the task didn't get a chance to call
659                 // Selector#wakeup. So we need to check task queue again before executing select operation.
660                 // If we don't, the task might be pended until select operation was timed out.
661                 // It might be pended until idle timeout if IdleStateHandler existed in pipeline.
662                 if (!runner.canBlock() && wakenUp.compareAndSet(false, true)) {
663                     selector.selectNow();
664                     selectCnt = 1;
665                     break;
666                 }
667 
668                 int selectedKeys = selector.select(timeoutMillis);
669                 selectCnt ++;
670 
671                 if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || !runner.canBlock()) {
672                     // - Selected something,
673                     // - waken up by user, or
674                     // - the task queue has a pending task.
675                     // - a scheduled task is ready for processing
676                     break;
677                 }
678                 if (Thread.interrupted()) {
679                     // Thread was interrupted so reset selected keys and break so we not run into a busy loop.
680                     // As this is most likely a bug in the handler of the user or it's client library we will
681                     // also log it.
682                     //
683                     // See https://github.com/netty/netty/issues/2426
684                     if (logger.isDebugEnabled()) {
685                         logger.debug("Selector.select() returned prematurely because " +
686                                 "Thread.currentThread().interrupt() was called. Use " +
687                                 "NioHandler.shutdownGracefully() to shutdown the NioHandler.");
688                     }
689                     selectCnt = 1;
690                     break;
691                 }
692 
693                 long time = System.nanoTime();
694                 if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
695                     // timeoutMillis elapsed without anything selected.
696                     selectCnt = 1;
697                 } else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
698                         selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
699                     // The code exists in an extra method to ensure the method is not too big to inline as this
700                     // branch is not very likely to get hit very frequently.
701                     selector = selectRebuildSelector(selectCnt);
702                     selectCnt = 1;
703                     break;
704                 }
705 
706                 currentTimeNanos = time;
707             }
708 
709             if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS) {
710                 if (logger.isDebugEnabled()) {
711                     logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.",
712                             selectCnt - 1, selector);
713                 }
714             }
715         } catch (CancelledKeyException e) {
716             if (logger.isDebugEnabled()) {
717                 logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?",
718                         selector, e);
719             }
720             // Harmless exception - log anyway
721         }
722     }
723 
724     private static long millisBeforeDeadline(long selectDeadLineNanos, long currentTimeNanos) {
725         assert selectDeadLineNanos != Long.MAX_VALUE;
726         long nanosBeforeDeadline = selectDeadLineNanos - currentTimeNanos;
727         // Prevent overflow when adding the rounding bias:
728         // if we don't do this, it would appear as the deadline is already reached!
729         if (nanosBeforeDeadline >= Long.MAX_VALUE - 500_000L) {
730             return Long.MAX_VALUE / 1_000_000L;
731         }
732         // Add 500_000 to round up to the nearest millisecond.
733         return (nanosBeforeDeadline + 500_000L) / 1_000_000L;
734     }
735 
736     int selectNow() throws IOException {
737         try {
738             return selector.selectNow();
739         } finally {
740             // restore wakeup state if needed
741             if (wakenUp.get()) {
742                 selector.wakeup();
743             }
744         }
745     }
746 
747     private Selector selectRebuildSelector(int selectCnt) throws IOException {
748         // The selector returned prematurely many times in a row.
749         // Rebuild the selector to work around the problem.
750         logger.warn(
751                 "Selector.select() returned prematurely {} times in a row; rebuilding Selector {}.",
752                 selectCnt, selector);
753 
754         rebuildSelector0();
755         Selector selector = this.selector;
756 
757         // Select again to populate selectedKeys.
758         selector.selectNow();
759         return selector;
760     }
761 
762     private void selectAgain() {
763         needsToSelectAgain = false;
764         try {
765             selector.selectNow();
766         } catch (Throwable t) {
767             logger.warn("Failed to update SelectionKeys.", t);
768         }
769     }
770 
771     /**
772      * Returns a new {@link IoHandlerFactory} that creates {@link NioIoHandler} instances
773      *
774      * @return factory                  the {@link IoHandlerFactory}.
775      */
776     public static IoHandlerFactory newFactory() {
777         return newFactory(SelectorProvider.provider(), DefaultSelectStrategyFactory.INSTANCE);
778     }
779 
780     /**
781      * Returns a new {@link IoHandlerFactory} that creates {@link NioIoHandler} instances.
782      *
783      * @param selectorProvider          the {@link SelectorProvider} to use.
784      * @return factory                  the {@link IoHandlerFactory}.
785      */
786     public static IoHandlerFactory newFactory(SelectorProvider selectorProvider) {
787         return newFactory(selectorProvider, DefaultSelectStrategyFactory.INSTANCE);
788     }
789 
790     /**
791      * Returns a new {@link IoHandlerFactory} that creates {@link NioIoHandler} instances.
792      *
793      * @param selectorProvider          the {@link SelectorProvider} to use.
794      * @param selectStrategyFactory     the {@link SelectStrategyFactory} to use.
795      * @return factory                  the {@link IoHandlerFactory}.
796      */
797     public static IoHandlerFactory newFactory(final SelectorProvider selectorProvider,
798                                               final SelectStrategyFactory selectStrategyFactory) {
799         ObjectUtil.checkNotNull(selectorProvider, "selectorProvider");
800         ObjectUtil.checkNotNull(selectStrategyFactory, "selectStrategyFactory");
801         return new IoHandlerFactory() {
802             @Override
803             public IoHandler newHandler(ThreadAwareExecutor executor) {
804                 return new NioIoHandler(executor, selectorProvider, selectStrategyFactory.newSelectStrategy());
805             }
806 
807             @Override
808             public boolean isChangingThreadSupported() {
809                 return true;
810             }
811         };
812     }
813 }