View Javadoc

1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.mina.core.polling;
21  
22  import java.net.SocketAddress;
23  import java.nio.channels.ClosedSelectorException;
24  import java.util.Collections;
25  import java.util.HashMap;
26  import java.util.HashSet;
27  import java.util.Iterator;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Queue;
31  import java.util.Set;
32  import java.util.concurrent.ConcurrentHashMap;
33  import java.util.concurrent.ConcurrentLinkedQueue;
34  import java.util.concurrent.Executor;
35  import java.util.concurrent.Executors;
36  
37  import org.apache.mina.core.RuntimeIoException;
38  import org.apache.mina.core.filterchain.IoFilter;
39  import org.apache.mina.core.service.AbstractIoAcceptor;
40  import org.apache.mina.core.service.IoAcceptor;
41  import org.apache.mina.core.service.IoHandler;
42  import org.apache.mina.core.service.IoProcessor;
43  import org.apache.mina.core.service.SimpleIoProcessorPool;
44  import org.apache.mina.core.session.AbstractIoSession;
45  import org.apache.mina.core.session.IoSession;
46  import org.apache.mina.core.session.IoSessionConfig;
47  import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
48  import org.apache.mina.util.ExceptionMonitor;
49  
50  /**
51   * A base class for implementing transport using a polling strategy. The
52   * underlying sockets will be checked in an active loop and woke up when an
53   * socket needed to be processed. This class handle the logic behind binding,
54   * accepting and disposing the server sockets. An {@link Executor} will be used
55   * for running client accepting and an {@link AbstractPollingIoProcessor} will
56   * be used for processing client I/O operations like reading, writing and
57   * closing.
58   * 
59   * All the low level methods for binding, accepting, closing need to be provided
60   * by the subclassing implementation.
61   * 
62   * @see NioSocketAcceptor for a example of implementation
63   * 
64   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
65   */
66  public abstract class AbstractPollingIoAcceptor<T extends AbstractIoSession, H>
67          extends AbstractIoAcceptor {
68  
69      private final IoProcessor<T> processor;
70  
71      private final boolean createdProcessor;
72  
73      private final Object lock = new Object();
74  
75      private final Queue<AcceptorOperationFuture> registerQueue = new ConcurrentLinkedQueue<AcceptorOperationFuture>();
76  
77      private final Queue<AcceptorOperationFuture> cancelQueue = new ConcurrentLinkedQueue<AcceptorOperationFuture>();
78  
79      private final Map<SocketAddress, H> boundHandles = Collections
80              .synchronizedMap(new HashMap<SocketAddress, H>());
81  
82      private final ServiceOperationFuture disposalFuture = new ServiceOperationFuture();
83  
84      /** A flag set when the acceptor has been created and initialized */
85      private volatile boolean selectable;
86  
87      /** The thread responsible of accepting incoming requests */ 
88      private Acceptor acceptor;
89  
90      /**
91       * Constructor for {@link AbstractPollingIoAcceptor}. You need to provide a default
92       * session configuration, a class of {@link IoProcessor} which will be instantiated in a
93       * {@link SimpleIoProcessorPool} for better scaling in multiprocessor systems. The default
94       * pool size will be used.
95       * 
96       * @see SimpleIoProcessorPool
97       * 
98       * @param sessionConfig
99       *            the default configuration for the managed {@link IoSession}
100      * @param processorClass a {@link Class} of {@link IoProcessor} for the associated {@link IoSession}
101      *            type.
102      */
103     protected AbstractPollingIoAcceptor(IoSessionConfig sessionConfig,
104             Class<? extends IoProcessor<T>> processorClass) {
105         this(sessionConfig, null, new SimpleIoProcessorPool<T>(processorClass),
106                 true);
107     }
108 
109     /**
110      * Constructor for {@link AbstractPollingIoAcceptor}. You need to provide a default
111      * session configuration, a class of {@link IoProcessor} which will be instantiated in a
112      * {@link SimpleIoProcessorPool} for using multiple thread for better scaling in multiprocessor
113      * systems.
114      * 
115      * @see SimpleIoProcessorPool
116      * 
117      * @param sessionConfig
118      *            the default configuration for the managed {@link IoSession}
119      * @param processorClass a {@link Class} of {@link IoProcessor} for the associated {@link IoSession}
120      *            type.
121      * @param processorCount the amount of processor to instantiate for the pool
122      */
123     protected AbstractPollingIoAcceptor(IoSessionConfig sessionConfig,
124             Class<? extends IoProcessor<T>> processorClass, int processorCount) {
125         this(sessionConfig, null, new SimpleIoProcessorPool<T>(processorClass,
126                 processorCount), true);
127     }
128 
129     /**
130      * Constructor for {@link AbstractPollingIoAcceptor}. You need to provide a default
131      * session configuration, a default {@link Executor} will be created using
132      * {@link Executors#newCachedThreadPool()}.
133      * 
134      * {@see AbstractIoService#AbstractIoService(IoSessionConfig, Executor)}
135      * 
136      * @param sessionConfig
137      *            the default configuration for the managed {@link IoSession}
138      * @param processor the {@link IoProcessor} for processing the {@link IoSession} of this transport, triggering 
139      *            events to the bound {@link IoHandler} and processing the chains of {@link IoFilter} 
140      */
141     protected AbstractPollingIoAcceptor(IoSessionConfig sessionConfig,
142             IoProcessor<T> processor) {
143         this(sessionConfig, null, processor, false);
144     }
145 
146     /**
147      * Constructor for {@link AbstractPollingIoAcceptor}. You need to provide a default
148      * session configuration and an {@link Executor} for handling I/O events. If a
149      * null {@link Executor} is provided, a default one will be created using
150      * {@link Executors#newCachedThreadPool()}.
151      * 
152      * {@see AbstractIoService#AbstractIoService(IoSessionConfig, Executor)}
153      * 
154      * @param sessionConfig
155      *            the default configuration for the managed {@link IoSession}
156      * @param executor
157      *            the {@link Executor} used for handling asynchronous execution of I/O
158      *            events. Can be <code>null</code>.
159      * @param processor the {@link IoProcessor} for processing the {@link IoSession} of this transport, triggering 
160      *            events to the bound {@link IoHandler} and processing the chains of {@link IoFilter} 
161      */
162     protected AbstractPollingIoAcceptor(IoSessionConfig sessionConfig,
163             Executor executor, IoProcessor<T> processor) {
164         this(sessionConfig, executor, processor, false);
165     }
166 
167     /**
168      * Constructor for {@link AbstractPollingIoAcceptor}. You need to provide a default
169      * session configuration and an {@link Executor} for handling I/O events. If a
170      * null {@link Executor} is provided, a default one will be created using
171      * {@link Executors#newCachedThreadPool()}.
172      * 
173      * {@see AbstractIoService#AbstractIoService(IoSessionConfig, Executor)}
174      * 
175      * @param sessionConfig
176      *            the default configuration for the managed {@link IoSession}
177      * @param executor
178      *            the {@link Executor} used for handling asynchronous execution of I/O
179      *            events. Can be <code>null</code>.
180      * @param processor the {@link IoProcessor} for processing the {@link IoSession} of 
181      * this transport, triggering events to the bound {@link IoHandler} and processing 
182      * the chains of {@link IoFilter}
183      * @param createdProcessor tagging the processor as automatically created, so it 
184      * will be automatically disposed 
185      */
186     private AbstractPollingIoAcceptor(IoSessionConfig sessionConfig,
187             Executor executor, IoProcessor<T> processor,
188             boolean createdProcessor) {
189         super(sessionConfig, executor);
190 
191         if (processor == null) {
192             throw new IllegalArgumentException("processor");
193         }
194 
195         this.processor = processor;
196         this.createdProcessor = createdProcessor;
197 
198         try {
199             // Initialize the selector
200             init();
201             
202             // The selector is now ready, we can switch the
203             // flag to true so that incoming connection can be accepted
204             selectable = true;
205         } catch (RuntimeException e) {
206             throw e;
207         } catch (Exception e) {
208             throw new RuntimeIoException("Failed to initialize.", e);
209         } finally {
210             if (!selectable) {
211                 try {
212                     destroy();
213                 } catch (Exception e) {
214                     ExceptionMonitor.getInstance().exceptionCaught(e);
215                 }
216             }
217         }
218     }
219 
220     /**
221      * Initialize the polling system, will be called at construction time.
222      * @throws Exception any exception thrown by the underlying system calls  
223      */
224     protected abstract void init() throws Exception;
225 
226     /**
227      * Destroy the polling system, will be called when this {@link IoAcceptor}
228      * implementation will be disposed.  
229      * @throws Exception any exception thrown by the underlying systems calls
230      */
231     protected abstract void destroy() throws Exception;
232 
233     /**
234      * Check for acceptable connections, interrupt when at least a server is ready for accepting.
235      * All the ready server socket descriptors need to be returned by {@link #selectedHandles()}
236      * @return The number of sockets having got incoming client
237      * @throws Exception any exception thrown by the underlying systems calls
238      */
239     protected abstract int select() throws Exception;
240 
241     /**
242      * Interrupt the {@link #select()} method. Used when the poll set need to be modified.
243      */
244     protected abstract void wakeup();
245 
246     /**
247      * {@link Iterator} for the set of server sockets found with acceptable incoming connections
248      *  during the last {@link #select()} call.
249      * @return the list of server handles ready
250      */
251     protected abstract Iterator<H> selectedHandles();
252 
253     /**
254      * Open a server socket for a given local address.
255      * @param localAddress the associated local address
256      * @return the opened server socket
257      * @throws Exception any exception thrown by the underlying systems calls
258      */
259     protected abstract H open(SocketAddress localAddress) throws Exception;
260 
261     /**
262      * Get the local address associated with a given server socket
263      * @param handle the server socket
264      * @return the local {@link SocketAddress} associated with this handle
265      * @throws Exception any exception thrown by the underlying systems calls
266      */
267     protected abstract SocketAddress localAddress(H handle) throws Exception;
268 
269     /**
270      * Accept a client connection for a server socket and return a new {@link IoSession}
271      * associated with the given {@link IoProcessor}
272      * @param processor the {@link IoProcessor} to associate with the {@link IoSession}  
273      * @param handle the server handle
274      * @return the created {@link IoSession}
275      * @throws Exception any exception thrown by the underlying systems calls
276      */
277     protected abstract T accept(IoProcessor<T> processor, H handle)
278             throws Exception;
279 
280     /**
281      * Close a server socket.
282      * @param handle the server socket
283      * @throws Exception any exception thrown by the underlying systems calls
284      */
285     protected abstract void close(H handle) throws Exception;
286 
287     /**
288      * {@inheritDoc}
289      */
290     @Override
291     protected void dispose0() throws Exception {
292         unbind();
293 
294         startupAcceptor();
295         wakeup();
296     }
297 
298     /**
299      * {@inheritDoc}
300      */
301     @Override
302     protected final Set<SocketAddress> bindInternal(
303             List<? extends SocketAddress> localAddresses) throws Exception {
304         // Create a bind request as a Future operation. When the selector
305         // have handled the registration, it will signal this future.
306         AcceptorOperationFuture request = new AcceptorOperationFuture(
307                 localAddresses);
308 
309         // adds the Registration request to the queue for the Workers
310         // to handle
311         registerQueue.add(request);
312 
313         // creates the Acceptor instance and has the local
314         // executor kick it off.
315         startupAcceptor();
316         
317         // As we just started the acceptor, we have to unblock the select()
318         // in order to process the bind request we just have added to the 
319         // registerQueue.
320         wakeup();
321         
322         // Now, we wait until this request is completed.
323         request.awaitUninterruptibly();
324 
325         if (request.getException() != null) {
326             throw request.getException();
327         }
328 
329         // Update the local addresses.
330         // setLocalAddresses() shouldn't be called from the worker thread
331         // because of deadlock.
332         Set<SocketAddress> newLocalAddresses = new HashSet<SocketAddress>();
333         
334         for (H handle:boundHandles.values()) {
335             newLocalAddresses.add(localAddress(handle));
336         }
337 
338         return newLocalAddresses;
339     }
340 
341     /**
342      * This method is called by the doBind() and doUnbind()
343      * methods.  If the acceptor is null, the acceptor object will
344      * be created and kicked off by the executor.  If the acceptor
345      * object is null, probably already created and this class
346      * is now working, then nothing will happen and the method
347      * will just return.
348      */
349     private void startupAcceptor() {
350         // If the acceptor is not ready, clear the queues
351         // TODO : they should already be clean : do we have to do that ?
352         if (!selectable) {
353             registerQueue.clear();
354             cancelQueue.clear();
355         }
356 
357         // start the acceptor if not already started
358         synchronized (lock) {
359             if (acceptor == null) {
360                 acceptor = new Acceptor();
361                 executeWorker(acceptor);
362             }
363         }
364     }
365 
366     /**
367      * {@inheritDoc}
368      */
369     @Override
370     protected final void unbind0(List<? extends SocketAddress> localAddresses)
371             throws Exception {
372         AcceptorOperationFuture future = new AcceptorOperationFuture(
373                 localAddresses);
374 
375         cancelQueue.add(future);
376         startupAcceptor();
377         wakeup();
378 
379         future.awaitUninterruptibly();
380         if (future.getException() != null) {
381             throw future.getException();
382         }
383     }
384 
385     /**
386      * This class is called by the startupAcceptor() method and is
387      * placed into a NamePreservingRunnable class.
388      * It's a thread accepting incoming connections from clients.
389      * The loop is stopped when all the bound handlers are unbound.
390      */
391     private class Acceptor implements Runnable {
392         public void run() {
393             int nHandles = 0;
394 
395             while (selectable) {
396                 try {
397                     // Detect if we have some keys ready to be processed
398                     // The select() will be woke up if some new connection
399                     // have occurred, or if the selector has been explicitly
400                     // woke up
401                     int selected = select();
402 
403                     // this actually sets the selector to OP_ACCEPT,
404                     // and binds to the port on which this class will
405                     // listen on
406                     nHandles += registerHandles();
407 
408                     if (selected > 0) {
409                         // We have some connection request, let's process 
410                         // them here. 
411                         processHandles(selectedHandles());
412                     }
413 
414                     // check to see if any cancellation request has been made.
415                     nHandles -= unregisterHandles();
416 
417                     // Now, if the number of registred handles is 0, we can
418                     // quit the loop: we don't have any socket listening
419                     // for incoming connection.
420                     if (nHandles == 0) {
421                         synchronized (lock) {
422                             if (registerQueue.isEmpty()
423                                     && cancelQueue.isEmpty()) {
424                                 acceptor = null;
425                                 break;
426                             }
427                         }
428                     }
429                 } catch (ClosedSelectorException cse) {
430                     // If the selector has been closed, we can exit the loop
431                     break;
432                 } catch (Throwable e) {
433                     ExceptionMonitor.getInstance().exceptionCaught(e);
434 
435                     try {
436                         Thread.sleep(1000);
437                     } catch (InterruptedException e1) {
438                         ExceptionMonitor.getInstance().exceptionCaught(e1);
439                     }
440                 }
441             }
442 
443             // Cleanup all the processors, and shutdown the acceptor.
444             if (selectable && isDisposing()) {
445                 selectable = false;
446                 try {
447                     if (createdProcessor) {
448                         processor.dispose();
449                     }
450                 } finally {
451                     try {
452                         synchronized (disposalLock) {
453                             if (isDisposing()) {
454                                 destroy();
455                             }
456                         }
457                     } catch (Exception e) {
458                         ExceptionMonitor.getInstance().exceptionCaught(e);
459                     } finally {
460                         disposalFuture.setDone();
461                     }
462                 }
463             }
464         }
465 
466         /**
467          * This method will process new sessions for the Worker class.  All
468          * keys that have had their status updates as per the Selector.selectedKeys()
469          * method will be processed here.  Only keys that are ready to accept
470          * connections are handled here.
471          * <p/>
472          * Session objects are created by making new instances of SocketSessionImpl
473          * and passing the session object to the SocketIoProcessor class.
474          */
475         @SuppressWarnings("unchecked")
476         private void processHandles(Iterator<H> handles) throws Exception {
477             while (handles.hasNext()) {
478                 H handle = handles.next();
479                 handles.remove();
480 
481                 // Associates a new created connection to a processor,
482                 // and get back a session
483                 T session = accept(processor, handle);
484                 
485                 if (session == null) {
486                     break;
487                 }
488 
489                 initSession(session, null, null);
490 
491                 // add the session to the SocketIoProcessor
492                 session.getProcessor().add(session);
493             }
494         }
495     }
496 
497     /**
498      * Sets up the socket communications.  Sets items such as:
499      * <p/>
500      * Blocking
501      * Reuse address
502      * Receive buffer size
503      * Bind to listen port
504      * Registers OP_ACCEPT for selector
505      */
506     private int registerHandles() {
507         for (;;) {
508             // The register queue contains the list of services to manage
509             // in this acceptor.
510             AcceptorOperationFuture future = registerQueue.poll();
511             
512             if (future == null) {
513                 return 0;
514             }
515 
516             // We create a temporary map to store the bound handles,
517             // as we may have to remove them all if there is an exception
518             // during the sockets opening.
519             Map<SocketAddress, H> newHandles = new ConcurrentHashMap<SocketAddress, H>();
520             List<SocketAddress> localAddresses = future.getLocalAddresses();
521 
522             try {
523                 // Process all the addresses
524                 for (SocketAddress a : localAddresses) {
525                     H handle = open(a);
526                     newHandles.put(localAddress(handle), handle);
527                 }
528 
529                 // Everything went ok, we can now update the map storing
530                 // all the bound sockets.
531                 boundHandles.putAll(newHandles);
532 
533                 // and notify.
534                 future.setDone();
535                 return newHandles.size();
536             } catch (Exception e) {
537                 // We store the exception in the future
538                 future.setException(e);
539             } finally {
540                 // Roll back if failed to bind all addresses.
541                 if (future.getException() != null) {
542                     for (H handle : newHandles.values()) {
543                         try {
544                             close(handle);
545                         } catch (Exception e) {
546                             ExceptionMonitor.getInstance().exceptionCaught(e);
547                         }
548                     }
549                     
550                     // TODO : add some comment : what is the wakeup() waking up ?
551                     wakeup();
552                 }
553             }
554         }
555     }
556 
557     /**
558      * This method just checks to see if anything has been placed into the
559      * cancellation queue.  The only thing that should be in the cancelQueue
560      * is CancellationRequest objects and the only place this happens is in
561      * the doUnbind() method.
562      */
563     private int unregisterHandles() {
564         int cancelledHandles = 0;
565         for (;;) {
566             AcceptorOperationFuture future = cancelQueue.poll();
567             if (future == null) {
568                 break;
569             }
570 
571             // close the channels
572             for (SocketAddress a : future.getLocalAddresses()) {
573                 H handle = boundHandles.remove(a);
574                 
575                 if (handle == null) {
576                     continue;
577                 }
578 
579                 try {
580                     close(handle);
581                     wakeup(); // wake up again to trigger thread death
582                 } catch (Throwable e) {
583                     ExceptionMonitor.getInstance().exceptionCaught(e);
584                 } finally {
585                     cancelledHandles++;
586                 }
587             }
588 
589             future.setDone();
590         }
591 
592         return cancelledHandles;
593     }
594 
595     /**
596      * {@inheritDoc}
597      */
598     public final IoSession newSession(SocketAddress remoteAddress,
599             SocketAddress localAddress) {
600         throw new UnsupportedOperationException();
601     }
602 }