View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
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   * <p>{@link SelectorManager} manages a number of {@link ManagedSelector}s that
54   * simplify the non-blocking primitives provided by the JVM via the {@code java.nio} package.</p>
55   * <p>{@link SelectorManager} subclasses implement methods to return protocol-specific
56   * {@link EndPoint}s and {@link Connection}s.</p>
57   */
58  public abstract class SelectorManager extends AbstractLifeCycle implements Dumpable
59  {
60      protected static final Logger LOG = Log.getLogger(SelectorManager.class);
61      /**
62       * The default connect timeout, in milliseconds
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       * Get the connect timeout
96       *
97       * @return the connect timeout (in milliseconds)
98       */
99      public long getConnectTimeout()
100     {
101         return _connectTimeout;
102     }
103 
104     /**
105      * Set the connect timeout (in milliseconds)
106      *
107      * @param milliseconds the number of milliseconds for the timeout
108      */
109     public void setConnectTimeout(long milliseconds)
110     {
111         _connectTimeout = milliseconds;
112     }
113 
114     /**
115      * Executes the given task in a different thread.
116      *
117      * @param task the task to execute
118      */
119     protected void execute(Runnable task)
120     {
121         executor.execute(task);
122     }
123 
124     /**
125      * @return the number of selectors in use
126      */
127     public int getSelectorCount()
128     {
129         return _selectors.length;
130     }
131 
132     private ManagedSelector chooseSelector()
133     {
134         // The ++ increment here is not atomic, but it does not matter,
135         // so long as the value changes sometimes, then connections will
136         // be distributed over the available selectors.
137         long s = _selectorIndex++;
138         int index = (int)(s % getSelectorCount());
139         return _selectors[index];
140     }
141 
142     /**
143      * <p>Registers a channel to perform a non-blocking connect.</p>
144      * <p>The channel must be set in non-blocking mode, and {@link SocketChannel#connect(SocketAddress)}
145      * must be called prior to calling this method.</p>
146      *
147      * @param channel    the channel to register
148      * @param attachment the attachment object
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      * <p>Registers a channel to perform non-blocking read/write operations.</p>
158      * <p>This method is called just after a channel has been accepted by {@link ServerSocketChannel#accept()},
159      * or just after having performed a blocking connect via {@link Socket#connect(SocketAddress, int)}.</p>
160      *
161      * @param channel the channel to register
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      * <p>Factory method for {@link ManagedSelector}.</p>
184      *
185      * @param id an identifier for the {@link ManagedSelector to create}
186      * @return a new {@link ManagedSelector}
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      * <p>Callback method invoked when an endpoint is opened.</p>
203      *
204      * @param endpoint the endpoint being opened
205      */
206     protected void endPointOpened(EndPoint endpoint)
207     {
208         endpoint.onOpen();
209     }
210 
211     /**
212      * <p>Callback method invoked when an endpoint is closed.</p>
213      *
214      * @param endpoint the endpoint being closed
215      */
216     protected void endPointClosed(EndPoint endpoint)
217     {
218         endpoint.onClose();
219     }
220 
221     /**
222      * <p>Callback method invoked when a connection is opened.</p>
223      *
224      * @param connection the connection just opened
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      * <p>Callback method invoked when a connection is closed.</p>
240      *
241      * @param connection the connection just closed
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      * <p>Callback method invoked when a non-blocking connect cannot be completed.</p>
262      * <p>By default it just logs with level warning.</p>
263      *
264      * @param channel the channel that attempted the connect
265      * @param ex the exception that caused the connect to fail
266      * @param attachment the attachment object associated at registration
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      * <p>Factory method to create {@link EndPoint}.</p>
275      * <p>This method is invoked as a result of the registration of a channel via {@link #connect(SocketChannel, Object)}
276      * or {@link #accept(SocketChannel)}.</p>
277      *
278      * @param channel   the channel associated to the endpoint
279      * @param selector the selector the channel is registered to
280      * @param selectionKey      the selection key
281      * @return a new endpoint
282      * @throws IOException if the endPoint cannot be created
283      * @see #newConnection(SocketChannel, EndPoint, Object)
284      */
285     protected abstract EndPoint newEndPoint(SocketChannel channel, SelectorManager.ManagedSelector selector, SelectionKey selectionKey) throws IOException;
286 
287     /**
288      * <p>Factory method to create {@link Connection}.</p>
289      *
290      * @param channel    the channel associated to the connection
291      * @param endpoint   the endpoint
292      * @param attachment the attachment
293      * @return a new connection
294      * @throws IOException
295      * @see #newEndPoint(SocketChannel, ManagedSelector, SelectionKey)
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      * <p>{@link ManagedSelector} wraps a {@link Selector} simplifying non-blocking operations on channels.</p>
314      * <p>{@link ManagedSelector} runs the select loop, which waits on {@link Selector#select()} until events
315      * happen for registered channels. When events happen, it notifies the {@link EndPoint} associated
316      * with the channel.</p>
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          * <p>Submits a change to be executed in the selector thread.</p>
353          * <p>Changes may be submitted from any thread, and the selector thread woken up
354          * (if necessary) to execute the change.</p>
355          *
356          * @param change the change to submit
357          */
358         public void submit(Runnable change)
359         {
360             // if we have been called by the selector thread we can directly run the change
361             if (_thread==Thread.currentThread())
362             {
363                 // If we are already iterating over the changes, just add this change to the list.
364                 // No race here because it is this thread that is iterating over the changes.
365                 if (_runningChanges)
366                     _changes.offer(change);
367                 else
368                 {
369                     // Otherwise we run the queued changes
370                     runChanges();
371                     // and then directly run the passed change
372                     runChange(change);
373                 }
374             }
375             else
376             {
377                 // otherwise we have to queue the change and wakeup the selector
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          * <p>Process changes and waits on {@link Selector#select()}.</p>
432          *
433          * @see #submit(Runnable)
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             // If tasks are submitted between these 2 statements, they will not
482             // wakeup the selector, therefore below we run again the tasks
483 
484             _needsWakeup = true;
485 
486             // Run again the tasks to avoid the race condition where a task is
487             // submitted but will not wake up the selector
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                         // This will unregister the channel from the selector
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                             // We are closing the SelectorManager, so we want to block the
793                             // selector thread here until we have closed all EndPoints.
794                             // This is different than calling close() directly, because close()
795                             // can wait forever, while here we are limited by the stop timeout.
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      * A {@link SelectableEndPoint} is an {@link EndPoint} that wish to be notified of
860      * non-blocking events by the {@link ManagedSelector}.
861      */
862     public interface SelectableEndPoint extends EndPoint
863     {
864         /**
865          * <p>Callback method invoked when a read or write events has been detected by the {@link ManagedSelector}
866          * for this endpoint.</p>
867          */
868         void onSelected();
869     }
870 }