1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.io;
20
21 import java.io.Closeable;
22 import java.io.IOException;
23 import java.net.ConnectException;
24 import java.net.Socket;
25 import java.net.SocketAddress;
26 import java.net.SocketTimeoutException;
27 import java.nio.channels.CancelledKeyException;
28 import java.nio.channels.ClosedChannelException;
29 import java.nio.channels.ClosedSelectorException;
30 import java.nio.channels.SelectionKey;
31 import java.nio.channels.Selector;
32 import java.nio.channels.ServerSocketChannel;
33 import java.nio.channels.SocketChannel;
34 import java.util.ArrayList;
35 import java.util.List;
36 import java.util.Queue;
37 import java.util.Set;
38 import java.util.concurrent.CountDownLatch;
39 import java.util.concurrent.Executor;
40 import java.util.concurrent.TimeUnit;
41 import java.util.concurrent.atomic.AtomicBoolean;
42
43 import org.eclipse.jetty.util.ConcurrentArrayQueue;
44 import org.eclipse.jetty.util.TypeUtil;
45 import org.eclipse.jetty.util.component.AbstractLifeCycle;
46 import org.eclipse.jetty.util.component.ContainerLifeCycle;
47 import org.eclipse.jetty.util.component.Dumpable;
48 import org.eclipse.jetty.util.log.Log;
49 import org.eclipse.jetty.util.log.Logger;
50 import org.eclipse.jetty.util.thread.Scheduler;
51
52
53
54
55
56
57
58 public abstract class SelectorManager extends AbstractLifeCycle implements Dumpable
59 {
60 protected static final Logger LOG = Log.getLogger(SelectorManager.class);
61
62
63
64 public static final int DEFAULT_CONNECT_TIMEOUT = 15000;
65
66 private final Executor executor;
67 private final Scheduler scheduler;
68 private final ManagedSelector[] _selectors;
69 private long _connectTimeout = DEFAULT_CONNECT_TIMEOUT;
70 private long _selectorIndex;
71
72 protected SelectorManager(Executor executor, Scheduler scheduler)
73 {
74 this(executor, scheduler, (Runtime.getRuntime().availableProcessors() + 1) / 2);
75 }
76
77 protected SelectorManager(Executor executor, Scheduler scheduler, int selectors)
78 {
79 this.executor = executor;
80 this.scheduler = scheduler;
81 _selectors = new ManagedSelector[selectors];
82 }
83
84 public Executor getExecutor()
85 {
86 return executor;
87 }
88
89 public Scheduler getScheduler()
90 {
91 return scheduler;
92 }
93
94
95
96
97
98
99 public long getConnectTimeout()
100 {
101 return _connectTimeout;
102 }
103
104
105
106
107
108
109 public void setConnectTimeout(long milliseconds)
110 {
111 _connectTimeout = milliseconds;
112 }
113
114
115
116
117
118
119 protected void execute(Runnable task)
120 {
121 executor.execute(task);
122 }
123
124
125
126
127 public int getSelectorCount()
128 {
129 return _selectors.length;
130 }
131
132 private ManagedSelector chooseSelector()
133 {
134
135
136
137 long s = _selectorIndex++;
138 int index = (int)(s % getSelectorCount());
139 return _selectors[index];
140 }
141
142
143
144
145
146
147
148
149
150 public void connect(SocketChannel channel, Object attachment)
151 {
152 ManagedSelector set = chooseSelector();
153 set.submit(set.new Connect(channel, attachment));
154 }
155
156
157
158
159
160
161
162
163 public void accept(final SocketChannel channel)
164 {
165 final ManagedSelector selector = chooseSelector();
166 selector.submit(selector.new Accept(channel));
167 }
168
169 @Override
170 protected void doStart() throws Exception
171 {
172 super.doStart();
173 for (int i = 0; i < _selectors.length; i++)
174 {
175 ManagedSelector selector = newSelector(i);
176 _selectors[i] = selector;
177 selector.start();
178 execute(selector);
179 }
180 }
181
182
183
184
185
186
187
188 protected ManagedSelector newSelector(int id)
189 {
190 return new ManagedSelector(id);
191 }
192
193 @Override
194 protected void doStop() throws Exception
195 {
196 for (ManagedSelector selector : _selectors)
197 selector.stop();
198 super.doStop();
199 }
200
201
202
203
204
205
206 protected void endPointOpened(EndPoint endpoint)
207 {
208 endpoint.onOpen();
209 }
210
211
212
213
214
215
216 protected void endPointClosed(EndPoint endpoint)
217 {
218 endpoint.onClose();
219 }
220
221
222
223
224
225
226 public void connectionOpened(Connection connection)
227 {
228 try
229 {
230 connection.onOpen();
231 }
232 catch (Exception x)
233 {
234 LOG.info("Exception while notifying connection " + connection, x);
235 }
236 }
237
238
239
240
241
242
243 public void connectionClosed(Connection connection)
244 {
245 try
246 {
247 connection.onClose();
248 }
249 catch (Exception x)
250 {
251 LOG.info("Exception while notifying connection " + connection, x);
252 }
253 }
254
255 protected boolean finishConnect(SocketChannel channel) throws IOException
256 {
257 return channel.finishConnect();
258 }
259
260
261
262
263
264
265
266
267
268 protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment)
269 {
270 LOG.warn(String.format("%s - %s", channel, attachment), ex);
271 }
272
273
274
275
276
277
278
279
280
281
282
283
284
285 protected abstract EndPoint newEndPoint(SocketChannel channel, SelectorManager.ManagedSelector selector, SelectionKey selectionKey) throws IOException;
286
287
288
289
290
291
292
293
294
295
296
297 public abstract Connection newConnection(SocketChannel channel, EndPoint endpoint, Object attachment) throws IOException;
298
299 @Override
300 public String dump()
301 {
302 return ContainerLifeCycle.dump(this);
303 }
304
305 @Override
306 public void dump(Appendable out, String indent) throws IOException
307 {
308 ContainerLifeCycle.dumpObject(out, this);
309 ContainerLifeCycle.dump(out, indent, TypeUtil.asList(_selectors));
310 }
311
312
313
314
315
316
317
318 public class ManagedSelector extends AbstractLifeCycle implements Runnable, Dumpable
319 {
320 private final Queue<Runnable> _changes = new ConcurrentArrayQueue<>();
321
322 private final int _id;
323 private Selector _selector;
324 private volatile Thread _thread;
325 private boolean _needsWakeup = true;
326 private boolean _runningChanges = false;
327
328 public ManagedSelector(int id)
329 {
330 _id = id;
331 setStopTimeout(5000);
332 }
333
334 @Override
335 protected void doStart() throws Exception
336 {
337 super.doStart();
338 _selector = Selector.open();
339 }
340
341 @Override
342 protected void doStop() throws Exception
343 {
344 LOG.debug("Stopping {}", this);
345 Stop stop = new Stop();
346 submit(stop);
347 stop.await(getStopTimeout());
348 LOG.debug("Stopped {}", this);
349 }
350
351
352
353
354
355
356
357
358 public void submit(Runnable change)
359 {
360
361 if (_thread==Thread.currentThread())
362 {
363
364
365 if (_runningChanges)
366 _changes.offer(change);
367 else
368 {
369
370 runChanges();
371
372 runChange(change);
373 }
374 }
375 else
376 {
377
378 _changes.offer(change);
379 LOG.debug("Queued change {}", change);
380 boolean wakeup = _needsWakeup;
381 if (wakeup)
382 wakeup();
383 }
384 }
385
386 private void runChanges()
387 {
388 try
389 {
390 if (_runningChanges)
391 throw new IllegalStateException();
392 _runningChanges=true;
393
394 Runnable change;
395 while ((change = _changes.poll()) != null)
396 runChange(change);
397 }
398 finally
399 {
400 _runningChanges=false;
401 }
402 }
403
404 protected void runChange(Runnable change)
405 {
406 LOG.debug("Running change {}", change);
407 change.run();
408 }
409
410 @Override
411 public void run()
412 {
413 _thread = Thread.currentThread();
414 String name = _thread.getName();
415 try
416 {
417 _thread.setName(name + "-selector-" + _id);
418 LOG.debug("Starting {} on {}", _thread, this);
419 while (isRunning())
420 select();
421 processChanges();
422 }
423 finally
424 {
425 LOG.debug("Stopped {} on {}", _thread, this);
426 _thread.setName(name);
427 }
428 }
429
430
431
432
433
434
435 public void select()
436 {
437 boolean debug = LOG.isDebugEnabled();
438 try
439 {
440 processChanges();
441
442 if (debug)
443 LOG.debug("Selector loop waiting on select");
444 int selected = _selector.select();
445 if (debug)
446 LOG.debug("Selector loop woken up from select, {}/{} selected", selected, _selector.keys().size());
447
448 _needsWakeup = false;
449
450 Set<SelectionKey> selectedKeys = _selector.selectedKeys();
451 for (SelectionKey key : selectedKeys)
452 {
453 if (key.isValid())
454 {
455 processKey(key);
456 }
457 else
458 {
459 if (debug)
460 LOG.debug("Selector loop ignoring invalid key for channel {}", key.channel());
461 Object attachment = key.attachment();
462 if (attachment instanceof EndPoint)
463 ((EndPoint)attachment).close();
464 }
465 }
466 selectedKeys.clear();
467 }
468 catch (Exception x)
469 {
470 if (isRunning())
471 LOG.warn(x);
472 else
473 LOG.ignore(x);
474 }
475 }
476
477 private void processChanges()
478 {
479 runChanges();
480
481
482
483
484 _needsWakeup = true;
485
486
487
488 runChanges();
489 }
490
491 private void processKey(SelectionKey key)
492 {
493 Object attachment = key.attachment();
494 try
495 {
496 if (attachment instanceof SelectableEndPoint)
497 {
498 ((SelectableEndPoint)attachment).onSelected();
499 }
500 else if (key.isConnectable())
501 {
502 processConnect(key, (Connect)attachment);
503 }
504 else
505 {
506 throw new IllegalStateException();
507 }
508 }
509 catch (CancelledKeyException x)
510 {
511 LOG.debug("Ignoring cancelled key for channel {}", key.channel());
512 if (attachment instanceof EndPoint)
513 ((EndPoint)attachment).close();
514 }
515 catch (Exception x)
516 {
517 LOG.warn("Could not process key for channel " + key.channel(), x);
518 if (attachment instanceof EndPoint)
519 ((EndPoint)attachment).close();
520 }
521 }
522
523 private void processConnect(SelectionKey key, Connect connect)
524 {
525 key.attach(connect.attachment);
526 SocketChannel channel = (SocketChannel)key.channel();
527 try
528 {
529 boolean connected = finishConnect(channel);
530 if (connected)
531 {
532 connect.timeout.cancel();
533 key.interestOps(0);
534 EndPoint endpoint = createEndPoint(channel, key);
535 key.attach(endpoint);
536 }
537 else
538 {
539 throw new ConnectException();
540 }
541 }
542 catch (Exception x)
543 {
544 connect.failed(x);
545 closeNoExceptions(channel);
546 }
547 }
548
549 private void closeNoExceptions(Closeable closeable)
550 {
551 try
552 {
553 closeable.close();
554 }
555 catch (IOException x)
556 {
557 LOG.ignore(x);
558 }
559 }
560
561 public void wakeup()
562 {
563 _selector.wakeup();
564 }
565
566 public boolean isSelectorThread()
567 {
568 return Thread.currentThread() == _thread;
569 }
570
571 private EndPoint createEndPoint(SocketChannel channel, SelectionKey selectionKey) throws IOException
572 {
573 EndPoint endPoint = newEndPoint(channel, this, selectionKey);
574 endPointOpened(endPoint);
575 Connection connection = newConnection(channel, endPoint, selectionKey.attachment());
576 endPoint.setConnection(connection);
577 connectionOpened(connection);
578 LOG.debug("Created {}", endPoint);
579 return endPoint;
580 }
581
582 public void destroyEndPoint(EndPoint endPoint)
583 {
584 LOG.debug("Destroyed {}", endPoint);
585 Connection connection = endPoint.getConnection();
586 if (connection != null)
587 connectionClosed(connection);
588 endPointClosed(endPoint);
589 }
590
591 @Override
592 public String dump()
593 {
594 return ContainerLifeCycle.dump(this);
595 }
596
597 @Override
598 public void dump(Appendable out, String indent) throws IOException
599 {
600 out.append(String.valueOf(this)).append(" id=").append(String.valueOf(_id)).append("\n");
601
602 Thread selecting = _thread;
603
604 Object where = "not selecting";
605 StackTraceElement[] trace = selecting == null ? null : selecting.getStackTrace();
606 if (trace != null)
607 {
608 for (StackTraceElement t : trace)
609 if (t.getClassName().startsWith("org.eclipse.jetty."))
610 {
611 where = t;
612 break;
613 }
614 }
615
616 Selector selector = _selector;
617 if (selector != null && selector.isOpen())
618 {
619 final ArrayList<Object> dump = new ArrayList<>(selector.keys().size() * 2);
620 dump.add(where);
621
622 DumpKeys dumpKeys = new DumpKeys(dump);
623 submit(dumpKeys);
624 dumpKeys.await(5, TimeUnit.SECONDS);
625
626 ContainerLifeCycle.dump(out, indent, dump);
627 }
628 }
629
630 public void dumpKeysState(List<Object> dumps)
631 {
632 Selector selector = _selector;
633 Set<SelectionKey> keys = selector.keys();
634 dumps.add(selector + " keys=" + keys.size());
635 for (SelectionKey key : keys)
636 {
637 if (key.isValid())
638 dumps.add(key.attachment() + " iOps=" + key.interestOps() + " rOps=" + key.readyOps());
639 else
640 dumps.add(key.attachment() + " iOps=-1 rOps=-1");
641 }
642 }
643
644 @Override
645 public String toString()
646 {
647 Selector selector = _selector;
648 return String.format("%s keys=%d selected=%d",
649 super.toString(),
650 selector != null && selector.isOpen() ? selector.keys().size() : -1,
651 selector != null && selector.isOpen() ? selector.selectedKeys().size() : -1);
652 }
653
654 private class DumpKeys implements Runnable
655 {
656 private final CountDownLatch latch = new CountDownLatch(1);
657 private final List<Object> _dumps;
658
659 private DumpKeys(List<Object> dumps)
660 {
661 this._dumps = dumps;
662 }
663
664 @Override
665 public void run()
666 {
667 dumpKeysState(_dumps);
668 latch.countDown();
669 }
670
671 public boolean await(long timeout, TimeUnit unit)
672 {
673 try
674 {
675 return latch.await(timeout, unit);
676 }
677 catch (InterruptedException x)
678 {
679 return false;
680 }
681 }
682 }
683
684 private class Accept implements Runnable
685 {
686 private final SocketChannel _channel;
687
688 public Accept(SocketChannel channel)
689 {
690 this._channel = channel;
691 }
692
693 @Override
694 public void run()
695 {
696 try
697 {
698 SelectionKey key = _channel.register(_selector, 0, null);
699 EndPoint endpoint = createEndPoint(_channel, key);
700 key.attach(endpoint);
701 }
702 catch (IOException x)
703 {
704 LOG.debug(x);
705 }
706 }
707 }
708
709 private class Connect implements Runnable
710 {
711 private final AtomicBoolean failed = new AtomicBoolean();
712 private final SocketChannel channel;
713 private final Object attachment;
714 private final Scheduler.Task timeout;
715
716 public Connect(SocketChannel channel, Object attachment)
717 {
718 this.channel = channel;
719 this.attachment = attachment;
720 this.timeout = scheduler.schedule(new ConnectTimeout(this), getConnectTimeout(), TimeUnit.MILLISECONDS);
721 }
722
723 @Override
724 public void run()
725 {
726 try
727 {
728 channel.register(_selector, SelectionKey.OP_CONNECT, this);
729 }
730 catch (ClosedSelectorException | ClosedChannelException x)
731 {
732 LOG.debug(x);
733 }
734 }
735
736 protected void failed(Throwable failure)
737 {
738 if (failed.compareAndSet(false, true))
739 connectionFailed(channel, failure, attachment);
740 }
741 }
742
743 private class ConnectTimeout implements Runnable
744 {
745 private final Connect connect;
746
747 private ConnectTimeout(Connect connect)
748 {
749 this.connect = connect;
750 }
751
752 @Override
753 public void run()
754 {
755 SocketChannel channel = connect.channel;
756 if (channel.isConnectionPending())
757 {
758 LOG.debug("Channel {} timed out while connecting, closing it", channel);
759 try
760 {
761
762 channel.close();
763 }
764 catch (IOException x)
765 {
766 LOG.ignore(x);
767 }
768 finally
769 {
770 connect.failed(new SocketTimeoutException());
771 }
772 }
773 }
774 }
775
776 private class Stop implements Runnable
777 {
778 private final CountDownLatch latch = new CountDownLatch(1);
779
780 @Override
781 public void run()
782 {
783 try
784 {
785 for (SelectionKey key : _selector.keys())
786 {
787 Object attachment = key.attachment();
788 if (attachment instanceof EndPoint)
789 {
790 EndPointCloser closer = new EndPointCloser((EndPoint)attachment);
791 execute(closer);
792
793
794
795
796 closer.await(getStopTimeout());
797 }
798 }
799
800 closeNoExceptions(_selector);
801 }
802 finally
803 {
804 latch.countDown();
805 }
806 }
807
808 public boolean await(long timeout)
809 {
810 try
811 {
812 return latch.await(timeout, TimeUnit.MILLISECONDS);
813 }
814 catch (InterruptedException x)
815 {
816 return false;
817 }
818 }
819 }
820
821 private class EndPointCloser implements Runnable
822 {
823 private final CountDownLatch latch = new CountDownLatch(1);
824 private final EndPoint endPoint;
825
826 private EndPointCloser(EndPoint endPoint)
827 {
828 this.endPoint = endPoint;
829 }
830
831 @Override
832 public void run()
833 {
834 try
835 {
836 endPoint.getConnection().close();
837 }
838 finally
839 {
840 latch.countDown();
841 }
842 }
843
844 private boolean await(long timeout)
845 {
846 try
847 {
848 return latch.await(timeout, TimeUnit.MILLISECONDS);
849 }
850 catch (InterruptedException x)
851 {
852 return false;
853 }
854 }
855 }
856 }
857
858
859
860
861
862 public interface SelectableEndPoint extends EndPoint
863 {
864
865
866
867
868 void onSelected();
869 }
870 }