View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2012 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.nio.channels.CancelledKeyException;
27  import java.nio.channels.ClosedChannelException;
28  import java.nio.channels.SelectionKey;
29  import java.nio.channels.Selector;
30  import java.nio.channels.ServerSocketChannel;
31  import java.nio.channels.SocketChannel;
32  import java.util.ArrayList;
33  import java.util.List;
34  import java.util.Queue;
35  import java.util.Set;
36  import java.util.concurrent.ConcurrentLinkedQueue;
37  import java.util.concurrent.CountDownLatch;
38  import java.util.concurrent.TimeUnit;
39  
40  import org.eclipse.jetty.util.ForkInvoker;
41  import org.eclipse.jetty.util.TypeUtil;
42  import org.eclipse.jetty.util.annotation.Name;
43  import org.eclipse.jetty.util.component.AbstractLifeCycle;
44  import org.eclipse.jetty.util.component.ContainerLifeCycle;
45  import org.eclipse.jetty.util.component.Dumpable;
46  import org.eclipse.jetty.util.log.Log;
47  import org.eclipse.jetty.util.log.Logger;
48  
49  /**
50   * <p>{@link SelectorManager} manages a number of {@link ManagedSelector}s that
51   * simplify the non-blocking primitives provided by the JVM via the {@code java.nio} package.</p>
52   * <p>{@link SelectorManager} subclasses implement methods to return protocol-specific
53   * {@link EndPoint}s and {@link Connection}s.</p>
54   */
55  public abstract class SelectorManager extends AbstractLifeCycle implements Dumpable
56  {
57      protected static final Logger LOG = Log.getLogger(SelectorManager.class);
58  
59      private final ManagedSelector[] _selectors;
60      private volatile long _selectorIndex;
61  
62      protected SelectorManager()
63      {
64          this((Runtime.getRuntime().availableProcessors() + 1) / 2);
65      }
66  
67      protected SelectorManager(@Name(value="selectors") int selectors)
68      {
69          _selectors = new ManagedSelector[selectors];
70      }
71  
72      /**
73       * Executes the given task in a different thread.
74       *
75       * @param task the task to execute
76       */
77      protected abstract void execute(Runnable task);
78  
79      /**
80       * @return the number of selectors in use
81       */
82      public int getSelectorCount()
83      {
84          return _selectors.length;
85      }
86  
87      private ManagedSelector chooseSelector()
88      {
89          // The ++ increment here is not atomic, but it does not matter,
90          // so long as the value changes sometimes, then connections will
91          // be distributed over the available selectors.
92          long s = _selectorIndex++;
93          int index = (int)(s % getSelectorCount());
94          return _selectors[index];
95      }
96  
97      /**
98       * <p>Registers a channel to perform a non-blocking connect.</p>
99       * <p>The channel must be set in non-blocking mode, and {@link SocketChannel#connect(SocketAddress)}
100      * must be called prior to calling this method.</p>
101      *
102      * @param channel    the channel to register
103      * @param attachment the attachment object
104      */
105     public void connect(SocketChannel channel, Object attachment)
106     {
107         ManagedSelector set = chooseSelector();
108         set.submit(set.new Connect(channel, attachment));
109     }
110 
111     /**
112      * <p>Registers a channel to perform non-blocking read/write operations.</p>
113      * <p>This method is called just after a channel has been accepted by {@link ServerSocketChannel#accept()},
114      * or just after having performed a blocking connect via {@link Socket#connect(SocketAddress, int)}.</p>
115      *
116      * @param channel the channel to register
117      */
118     public void accept(final SocketChannel channel)
119     {
120         final ManagedSelector selector = chooseSelector();
121         selector.submit(selector.new Accept(channel));
122     }
123 
124     @Override
125     protected void doStart() throws Exception
126     {
127         super.doStart();
128         for (int i = 0; i < _selectors.length; i++)
129         {
130             ManagedSelector selector = newSelector(i);
131             _selectors[i] = selector;
132             selector.start();
133             execute(selector);
134         }
135     }
136 
137     /**
138      * <p>Factory method for {@link ManagedSelector}.</p>
139      *
140      * @param id an identifier for the {@link ManagedSelector to create}
141      * @return a new {@link ManagedSelector}
142      */
143     protected ManagedSelector newSelector(int id)
144     {
145         return new ManagedSelector(id);
146     }
147 
148     @Override
149     protected void doStop() throws Exception
150     {
151         for (ManagedSelector selector : _selectors)
152             selector.stop();
153         super.doStop();
154     }
155 
156     /**
157      * <p>Callback method invoked when an endpoint is opened.</p>
158      *
159      * @param endpoint the endpoint being opened
160      */
161     protected void endPointOpened(EndPoint endpoint)
162     {
163         endpoint.onOpen();
164     }
165 
166     /**
167      * <p>Callback method invoked when an endpoint is closed.</p>
168      *
169      * @param endpoint the endpoint being closed
170      */
171     protected void endPointClosed(EndPoint endpoint)
172     {
173         endpoint.onClose();
174     }
175 
176     /**
177      * <p>Callback method invoked when a connection is opened.</p>
178      *
179      * @param connection the connection just opened
180      */
181     public void connectionOpened(Connection connection)
182     {
183         try
184         {
185             connection.onOpen();
186         }
187         catch (Exception x)
188         {
189             LOG.info("Exception while notifying connection " + connection, x);
190         }
191     }
192 
193     /**
194      * <p>Callback method invoked when a connection is closed.</p>
195      *
196      * @param connection the connection just closed
197      */
198     public void connectionClosed(Connection connection)
199     {
200         try
201         {
202             connection.onClose();
203         }
204         catch (Exception x)
205         {
206             LOG.info("Exception while notifying connection " + connection, x);
207         }
208     }
209 
210     /**
211      * <p>Callback method invoked when a non-blocking connect cannot be completed.</p>
212      * <p>By default it just logs with level warning.</p>
213      *
214      * @param channel the channel that attempted the connect
215      * @param ex the exception that caused the connect to fail
216      * @param attachment the attachment object associated at registration
217      */
218     protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment)
219     {
220         LOG.warn(String.format("%s - %s", channel, attachment), ex);
221     }
222 
223     /**
224      * <p>Factory method to create {@link EndPoint}.</p>
225      * <p>This method is invoked as a result of the registration of a channel via {@link #connect(SocketChannel, Object)}
226      * or {@link #accept(SocketChannel)}.</p>
227      *
228      * @param channel   the channel associated to the endpoint
229      * @param selector the selector the channel is registered to
230      * @param selectionKey      the selection key
231      * @return a new endpoint
232      * @throws IOException if the endPoint cannot be created
233      * @see #newConnection(SocketChannel, EndPoint, Object)
234      */
235     protected abstract EndPoint newEndPoint(SocketChannel channel, SelectorManager.ManagedSelector selector, SelectionKey selectionKey) throws IOException;
236 
237     /**
238      * <p>Factory method to create {@link Connection}.</p>
239      *
240      * @param channel    the channel associated to the connection
241      * @param endpoint   the endpoint
242      * @param attachment the attachment
243      * @return a new connection
244      * @throws IOException
245      * @see #newEndPoint(SocketChannel, ManagedSelector, SelectionKey)
246      */
247     public abstract Connection newConnection(SocketChannel channel, EndPoint endpoint, Object attachment) throws IOException;
248 
249     @Override
250     public String dump()
251     {
252         return ContainerLifeCycle.dump(this);
253     }
254 
255     @Override
256     public void dump(Appendable out, String indent) throws IOException
257     {
258         ContainerLifeCycle.dumpObject(out, this);
259         ContainerLifeCycle.dump(out, indent, TypeUtil.asList(_selectors));
260     }
261 
262     /**
263      * <p>{@link ManagedSelector} wraps a {@link Selector} simplifying non-blocking operations on channels.</p>
264      * <p>{@link ManagedSelector} runs the select loop, which waits on {@link Selector#select()} until events
265      * happen for registered channels. When events happen, it notifies the {@link EndPoint} associated
266      * with the channel.</p>
267      */
268     public class ManagedSelector extends AbstractLifeCycle implements Runnable, Dumpable
269     {
270         private final ForkInvoker<Runnable> invoker = new ManagedSelectorInvoker();
271         private final Queue<Runnable> _changes = new ConcurrentLinkedQueue<>();
272         private final int _id;
273         private Selector _selector;
274         private Thread _thread;
275         private boolean _needsWakeup = true;
276 
277         public ManagedSelector(int id)
278         {
279             _id = id;
280             setStopTimeout(5000);
281         }
282 
283         @Override
284         protected void doStart() throws Exception
285         {
286             super.doStart();
287             _selector = Selector.open();
288         }
289 
290         @Override
291         protected void doStop() throws Exception
292         {
293             LOG.debug("Stopping {}", this);
294             Stop stop = new Stop();
295             submit(stop);
296             stop.await(getStopTimeout());
297             LOG.debug("Stopped {}", this);
298         }
299 
300         /**
301          * <p>Submits a change to be executed in the selector thread.</p>
302          * <p>Changes may be submitted from any thread, and if they are submitted from a thread different
303          * from the selector thread, they are queued for execution, and the selector thread woken up
304          * (if necessary) to execute the change.</p>
305          *
306          * @param change the change to submit
307          * @return true if the change has been executed, false if it has been queued for later execution
308          */
309         public boolean submit(Runnable change)
310         {
311             return !invoker.invoke(change);
312         }
313 
314         private void runChanges()
315         {
316             Runnable change;
317             while ((change = _changes.poll()) != null)
318                 runChange(change);
319         }
320 
321         protected void runChange(Runnable change)
322         {
323             LOG.debug("Running change {}", change);
324             change.run();
325         }
326 
327         @Override
328         public void run()
329         {
330             _thread = Thread.currentThread();
331             String name = _thread.getName();
332             try
333             {
334                 _thread.setName(name + "-selector-" + _id);
335                 LOG.debug("Starting {} on {}", _thread, this);
336                 while (isRunning())
337                     select();
338                 processChanges();
339             }
340             finally
341             {
342                 LOG.debug("Stopped {} on {}", _thread, this);
343                 _thread.setName(name);
344             }
345         }
346 
347         /**
348          * <p>Process changes and waits on {@link Selector#select()}.</p>
349          *
350          * @see #submit(Runnable)
351          */
352         public void select()
353         {
354             boolean debug = LOG.isDebugEnabled();
355             try
356             {
357                 processChanges();
358 
359                 if (debug)
360                     LOG.debug("Selector loop waiting on select");
361                 int selected = _selector.select();
362                 if (debug)
363                     LOG.debug("Selector loop woken up from select, {}/{} selected", selected, _selector.keys().size());
364 
365                 _needsWakeup = false;
366 
367                 Set<SelectionKey> selectedKeys = _selector.selectedKeys();
368                 for (SelectionKey key : selectedKeys)
369                 {
370                     if (key.isValid())
371                     {
372                         processKey(key);
373                     }
374                     else
375                     {
376                         if (debug)
377                             LOG.debug("Selector loop ignoring invalid key for channel {}", key.channel());
378                         Object attachment = key.attachment();
379                         if (attachment instanceof EndPoint)
380                             ((EndPoint)attachment).close();
381                     }
382                 }
383                 selectedKeys.clear();
384             }
385             catch (Exception x)
386             {
387                 if (isRunning())
388                     LOG.warn(x);
389                 else
390                     LOG.ignore(x);
391             }
392         }
393 
394         private void processChanges()
395         {
396             runChanges();
397 
398             // If tasks are submitted between these 2 statements, they will not
399             // wakeup the selector, therefore below we run again the tasks
400 
401             _needsWakeup = true;
402 
403             // Run again the tasks to avoid the race condition where a task is
404             // submitted but will not wake up the selector
405             runChanges();
406         }
407 
408         private void processKey(SelectionKey key)
409         {
410             Object attachment = key.attachment();
411             try
412             {
413                 if (attachment instanceof SelectableEndPoint)
414                 {
415                     ((SelectableEndPoint)attachment).onSelected();
416                 }
417                 else if (key.isConnectable())
418                 {
419                     // Complete a connection of a registered channel
420                     SocketChannel channel = (SocketChannel)key.channel();
421                     try
422                     {
423                         boolean connected = channel.finishConnect();
424                         if (connected)
425                         {
426                             key.interestOps(0);
427                             EndPoint endpoint = createEndPoint(channel, key);
428                             key.attach(endpoint);
429                         }
430                         else
431                         {
432                             throw new ConnectException();
433                         }
434                     }
435                     catch (Exception x)
436                     {
437                         connectionFailed(channel, x, attachment);
438                         closeNoExceptions(channel);
439                     }
440                 }
441                 else
442                 {
443                     throw new IllegalStateException();
444                 }
445             }
446             catch (CancelledKeyException x)
447             {
448                 LOG.debug("Ignoring cancelled key for channel {}", key.channel());
449                 if (attachment instanceof EndPoint)
450                     ((EndPoint)attachment).close();
451             }
452             catch (Exception x)
453             {
454                 LOG.warn("Could not process key for channel " + key.channel(), x);
455                 if (attachment instanceof EndPoint)
456                     ((EndPoint)attachment).close();
457             }
458         }
459 
460         private void closeNoExceptions(Closeable closeable)
461         {
462             try
463             {
464                 closeable.close();
465             }
466             catch (IOException x)
467             {
468                 LOG.ignore(x);
469             }
470         }
471 
472         public SelectorManager getSelectorManager()
473         {
474             return SelectorManager.this;
475         }
476 
477         public void wakeup()
478         {
479             _selector.wakeup();
480         }
481 
482         private EndPoint createEndPoint(SocketChannel channel, SelectionKey selectionKey) throws IOException
483         {
484             EndPoint endPoint = newEndPoint(channel, this, selectionKey);
485             endPointOpened(endPoint);
486             Connection connection = newConnection(channel, endPoint, selectionKey.attachment());
487             endPoint.setConnection(connection);
488             connectionOpened(connection);
489             LOG.debug("Created {}", endPoint);
490             return endPoint;
491         }
492 
493         public void destroyEndPoint(EndPoint endPoint)
494         {
495             // TODO
496 
497             LOG.debug("Destroyed {}", endPoint);
498             Connection connection = endPoint.getConnection();
499             if (connection != null)
500                 connectionClosed(connection);
501             endPointClosed(endPoint);
502         }
503 
504         @Override
505         public String dump()
506         {
507             return ContainerLifeCycle.dump(this);
508         }
509 
510         @Override
511         public void dump(Appendable out, String indent) throws IOException
512         {
513             out.append(String.valueOf(this)).append(" id=").append(String.valueOf(_id)).append("\n");
514 
515             Thread selecting = _thread;
516 
517             Object where = "not selecting";
518             StackTraceElement[] trace = selecting == null ? null : selecting.getStackTrace();
519             if (trace != null)
520             {
521                 for (StackTraceElement t : trace)
522                     if (t.getClassName().startsWith("org.eclipse.jetty."))
523                     {
524                         where = t;
525                         break;
526                     }
527             }
528 
529             Selector selector = _selector;
530             if (selector != null)
531             {
532                 final ArrayList<Object> dump = new ArrayList<>(selector.keys().size() * 2);
533                 dump.add(where);
534 
535                 DumpKeys dumpKeys = new DumpKeys(dump);
536                 submit(dumpKeys);
537                 dumpKeys.await(5, TimeUnit.SECONDS);
538 
539                 ContainerLifeCycle.dump(out, indent, dump);
540             }
541         }
542 
543         public void dumpKeysState(List<Object> dumps)
544         {
545             Selector selector = _selector;
546             Set<SelectionKey> keys = selector.keys();
547             dumps.add(selector + " keys=" + keys.size());
548             for (SelectionKey key : keys)
549             {
550                 if (key.isValid())
551                     dumps.add(key.attachment() + " iOps=" + key.interestOps() + " rOps=" + key.readyOps());
552                 else
553                     dumps.add(key.attachment() + " iOps=-1 rOps=-1");
554             }
555         }
556 
557         @Override
558         public String toString()
559         {
560             Selector selector = _selector;
561             return String.format("%s keys=%d selected=%d",
562                     super.toString(),
563                     selector != null && selector.isOpen() ? selector.keys().size() : -1,
564                     selector != null && selector.isOpen() ? selector.selectedKeys().size() : -1);
565         }
566 
567         private class ManagedSelectorInvoker extends ForkInvoker<Runnable>
568         {
569             private ManagedSelectorInvoker()
570             {
571                 super(4);
572             }
573 
574             @Override
575             protected boolean condition()
576             {
577                 return Thread.currentThread() != _thread;
578             }
579 
580             @Override
581             public void fork(Runnable change)
582             {
583                 _changes.offer(change);
584                 LOG.debug("Queued change {}", change);
585                 boolean wakeup = _needsWakeup;
586                 if (wakeup)
587                     wakeup();
588             }
589 
590             @Override
591             public void call(Runnable change)
592             {
593                 LOG.debug("Submitted change {}", change);
594                 runChanges();
595                 runChange(change);
596             }
597         }
598 
599         private class DumpKeys implements Runnable
600         {
601             private final CountDownLatch latch = new CountDownLatch(1);
602             private final List<Object> _dumps;
603 
604             private DumpKeys(List<Object> dumps)
605             {
606                 this._dumps = dumps;
607             }
608 
609             @Override
610             public void run()
611             {
612                 dumpKeysState(_dumps);
613                 latch.countDown();
614             }
615 
616             public boolean await(long timeout, TimeUnit unit)
617             {
618                 try
619                 {
620                     return latch.await(timeout, unit);
621                 }
622                 catch (InterruptedException x)
623                 {
624                     return false;
625                 }
626             }
627         }
628 
629         private class Accept implements Runnable
630         {
631             private final SocketChannel _channel;
632 
633             public Accept(SocketChannel channel)
634             {
635                 this._channel = channel;
636             }
637 
638             @Override
639             public void run()
640             {
641                 try
642                 {
643                     SelectionKey key = _channel.register(_selector, 0, null);
644                     EndPoint endpoint = createEndPoint(_channel, key);
645                     key.attach(endpoint);
646                 }
647                 catch (IOException x)
648                 {
649                     LOG.debug(x);
650                 }
651             }
652         }
653 
654         private class Connect implements Runnable
655         {
656             private final SocketChannel channel;
657             private final Object attachment;
658 
659             public Connect(SocketChannel channel, Object attachment)
660             {
661                 this.channel = channel;
662                 this.attachment = attachment;
663             }
664 
665             @Override
666             public void run()
667             {
668                 try
669                 {
670                     channel.register(_selector, SelectionKey.OP_CONNECT, attachment);
671                 }
672                 catch (ClosedChannelException x)
673                 {
674                     LOG.debug(x);
675                 }
676             }
677         }
678 
679         private class Stop implements Runnable
680         {
681             private final CountDownLatch latch = new CountDownLatch(1);
682 
683             @Override
684             public void run()
685             {
686                 try
687                 {
688                     for (SelectionKey key : _selector.keys())
689                     {
690                         Object attachment = key.attachment();
691                         if (attachment instanceof EndPoint)
692                         {
693                             EndPointCloser closer = new EndPointCloser((EndPoint)attachment);
694                             execute(closer);
695                             // We are closing the SelectorManager, so we want to block the
696                             // selector thread here until we have closed all EndPoints.
697                             // This is different than calling close() directly, because close()
698                             // can wait forever, while here we are limited by the stop timeout.
699                             closer.await(getStopTimeout());
700                         }
701                     }
702 
703                     closeNoExceptions(_selector);
704                 }
705                 finally
706                 {
707                     latch.countDown();
708                 }
709             }
710 
711             public boolean await(long timeout)
712             {
713                 try
714                 {
715                     return latch.await(timeout, TimeUnit.MILLISECONDS);
716                 }
717                 catch (InterruptedException x)
718                 {
719                     return false;
720                 }
721             }
722         }
723 
724         private class EndPointCloser implements Runnable
725         {
726             private final CountDownLatch latch = new CountDownLatch(1);
727             private final EndPoint endPoint;
728 
729             private EndPointCloser(EndPoint endPoint)
730             {
731                 this.endPoint = endPoint;
732             }
733 
734             @Override
735             public void run()
736             {
737                 try
738                 {
739                     endPoint.getConnection().close();
740                 }
741                 finally
742                 {
743                     latch.countDown();
744                 }
745             }
746 
747             private boolean await(long timeout)
748             {
749                 try
750                 {
751                     return latch.await(timeout, TimeUnit.MILLISECONDS);
752                 }
753                 catch (InterruptedException x)
754                 {
755                     return false;
756                 }
757             }
758         }
759     }
760 
761     /**
762      * A {@link SelectableEndPoint} is an {@link EndPoint} that wish to be notified of
763      * non-blocking events by the {@link ManagedSelector}.
764      */
765     public interface SelectableEndPoint extends EndPoint
766     {
767         /**
768          * <p>Callback method invoked when a read or write events has been detected by the {@link ManagedSelector}
769          * for this endpoint.</p>
770          */
771         void onSelected();
772     }
773 }