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.ConnectException;
23  import java.net.SocketAddress;
24  import java.util.Iterator;
25  import java.util.Queue;
26  import java.util.concurrent.ConcurrentLinkedQueue;
27  import java.util.concurrent.Executor;
28  import java.util.concurrent.Executors;
29  
30  import org.apache.mina.core.RuntimeIoException;
31  import org.apache.mina.core.filterchain.IoFilter;
32  import org.apache.mina.core.future.ConnectFuture;
33  import org.apache.mina.core.future.DefaultConnectFuture;
34  import org.apache.mina.core.service.AbstractIoConnector;
35  import org.apache.mina.core.service.IoConnector;
36  import org.apache.mina.core.service.IoHandler;
37  import org.apache.mina.core.service.IoProcessor;
38  import org.apache.mina.core.service.SimpleIoProcessorPool;
39  import org.apache.mina.core.session.AbstractIoSession;
40  import org.apache.mina.core.session.IoSession;
41  import org.apache.mina.core.session.IoSessionConfig;
42  import org.apache.mina.core.session.IoSessionInitializer;
43  import org.apache.mina.transport.socket.nio.NioSocketConnector;
44  import org.apache.mina.util.ExceptionMonitor;
45  
46  /**
47   * A base class for implementing client transport using a polling strategy. The
48   * underlying sockets will be checked in an active loop and woke up when an
49   * socket needed to be processed. This class handle the logic behind binding,
50   * connecting and disposing the client sockets. A {@link Executor} will be used
51   * for running client connection, and an {@link AbstractPollingIoProcessor} will
52   * be used for processing connected client I/O operations like reading, writing
53   * and closing.
54   * 
55   * All the low level methods for binding, connecting, closing need to be
56   * provided by the subclassing implementation.
57   * 
58   * @see NioSocketConnector for a example of implementation
59   * 
60   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
61   */
62  public abstract class AbstractPollingIoConnector<T extends AbstractIoSession, H>
63          extends AbstractIoConnector {
64  
65      private final Object lock = new Object();
66      private final Queue<ConnectionRequest> connectQueue = new ConcurrentLinkedQueue<ConnectionRequest>();
67      private final Queue<ConnectionRequest> cancelQueue = new ConcurrentLinkedQueue<ConnectionRequest>();
68      private final IoProcessor<T> processor;
69      private final boolean createdProcessor;
70  
71      private final ServiceOperationFuture disposalFuture =
72          new ServiceOperationFuture();
73      private volatile boolean selectable;
74      
75      /** The connector thread */
76      private Connector connector;
77  
78      /**
79       * Constructor for {@link AbstractPollingIoConnector}. You need to provide a default
80       * session configuration, a class of {@link IoProcessor} which will be instantiated in a
81       * {@link SimpleIoProcessorPool} for better scaling in multiprocessor systems. The default
82       * pool size will be used.
83       * 
84       * @see SimpleIoProcessorPool
85       * 
86       * @param sessionConfig
87       *            the default configuration for the managed {@link IoSession}
88       * @param processorClass a {@link Class} of {@link IoProcessor} for the associated {@link IoSession}
89       *            type.
90       */
91      protected AbstractPollingIoConnector(IoSessionConfig sessionConfig, Class<? extends IoProcessor<T>> processorClass) {
92          this(sessionConfig, null, new SimpleIoProcessorPool<T>(processorClass), true);
93      }
94  
95      /**
96       * Constructor for {@link AbstractPollingIoConnector}. You need to provide a default
97       * session configuration, a class of {@link IoProcessor} which will be instantiated in a
98       * {@link SimpleIoProcessorPool} for using multiple thread for better scaling in multiprocessor
99       * systems.
100      * 
101      * @see SimpleIoProcessorPool
102      * 
103      * @param sessionConfig
104      *            the default configuration for the managed {@link IoSession}
105      * @param processorClass a {@link Class} of {@link IoProcessor} for the associated {@link IoSession}
106      *            type.
107      * @param processorCount the amount of processor to instantiate for the pool
108      */
109     protected AbstractPollingIoConnector(IoSessionConfig sessionConfig, Class<? extends IoProcessor<T>> processorClass, int processorCount) {
110         this(sessionConfig, null, new SimpleIoProcessorPool<T>(processorClass, processorCount), true);
111     }
112 
113     /**
114      * Constructor for {@link AbstractPollingIoConnector}. You need to provide a default
115      * session configuration, a default {@link Executor} will be created using
116      * {@link Executors#newCachedThreadPool()}.
117      * 
118      * {@see AbstractIoService#AbstractIoService(IoSessionConfig, Executor)}
119      * 
120      * @param sessionConfig
121      *            the default configuration for the managed {@link IoSession}
122      * @param processor the {@link IoProcessor} for processing the {@link IoSession} of this transport, triggering 
123      *            events to the bound {@link IoHandler} and processing the chains of {@link IoFilter} 
124      */
125     protected AbstractPollingIoConnector(IoSessionConfig sessionConfig, IoProcessor<T> processor) {
126         this(sessionConfig, null, processor, false);
127     }
128 
129     /**
130      * Constructor for {@link AbstractPollingIoConnector}. You need to provide a default
131      * session configuration and an {@link Executor} for handling I/O events. If
132      * null {@link Executor} is provided, a default one will be created using
133      * {@link Executors#newCachedThreadPool()}.
134      * 
135      * {@see AbstractIoService#AbstractIoService(IoSessionConfig, Executor)}
136      * 
137      * @param sessionConfig
138      *            the default configuration for the managed {@link IoSession}
139      * @param executor
140      *            the {@link Executor} used for handling asynchronous execution of I/O
141      *            events. Can be <code>null</code>.
142      * @param processor the {@link IoProcessor} for processing the {@link IoSession} of this transport, triggering 
143      *            events to the bound {@link IoHandler} and processing the chains of {@link IoFilter} 
144      */
145     protected AbstractPollingIoConnector(IoSessionConfig sessionConfig, Executor executor, IoProcessor<T> processor) {
146         this(sessionConfig, executor, processor, false);
147     }
148 
149     /**
150      * Constructor for {@link AbstractPollingIoAcceptor}. You need to provide a default
151      * session configuration and an {@link Executor} for handling I/O events. If
152      * null {@link Executor} is provided, a default one will be created using
153      * {@link Executors#newCachedThreadPool()}.
154      * 
155      * {@see AbstractIoService#AbstractIoService(IoSessionConfig, Executor)}
156      * 
157      * @param sessionConfig
158      *            the default configuration for the managed {@link IoSession}
159      * @param executor
160      *            the {@link Executor} used for handling asynchronous execution of I/O
161      *            events. Can be <code>null</code>.
162      * @param processor the {@link IoProcessor} for processing the {@link IoSession} of this transport, triggering 
163      *            events to the bound {@link IoHandler} and processing the chains of {@link IoFilter}
164      * @param createdProcessor tagging the processor as automatically created, so it will be automatically disposed 
165      */
166     private AbstractPollingIoConnector(IoSessionConfig sessionConfig, Executor executor, IoProcessor<T> processor, boolean createdProcessor) {
167         super(sessionConfig, executor);
168 
169         if (processor == null) {
170             throw new IllegalArgumentException("processor");
171         }
172 
173         this.processor = processor;
174         this.createdProcessor = createdProcessor;
175 
176         try {
177             init();
178             selectable = true;
179         } catch (RuntimeException e){
180             throw e;
181         } catch (Exception e) {
182             throw new RuntimeIoException("Failed to initialize.", e);
183         } finally {
184             if (!selectable) {
185                 try {
186                     destroy();
187                 } catch (Exception e) {
188                     ExceptionMonitor.getInstance().exceptionCaught(e);
189                 }
190             }
191         }
192     }
193 
194     /**
195      * Initialize the polling system, will be called at construction time.
196      * @throws Exception any exception thrown by the underlying system calls  
197      */
198     protected abstract void init() throws Exception;
199 
200     /**
201      * Destroy the polling system, will be called when this {@link IoConnector}
202      * implementation will be disposed.  
203      * @throws Exception any exception thrown by the underlying systems calls
204      */
205     protected abstract void destroy() throws Exception;
206     
207     /**
208      * Create a new client socket handle from a local {@link SocketAddress}
209      * @param localAddress the socket address for binding the new client socket 
210      * @return a new client socket handle 
211      * @throws Exception any exception thrown by the underlying systems calls
212      */
213     protected abstract H newHandle(SocketAddress localAddress) throws Exception;
214     
215     /**
216      * Connect a newly created client socket handle to a remote {@link SocketAddress}.
217      * This operation is non-blocking, so at end of the call the socket can be still in connection
218      * process.
219      * @param handle the client socket handle
220      * @param remoteAddress the remote address where to connect
221      * @return <tt>true</tt> if a connection was established, <tt>false</tt> if this client socket 
222      *         is in non-blocking mode and the connection operation is in progress
223      * @throws Exception
224      */
225     protected abstract boolean connect(H handle, SocketAddress remoteAddress) throws Exception;
226     
227     /**
228      * Finish the connection process of a client socket after it was marked as ready to process
229      * by the {@link #select(int)} call. The socket will be connected or reported as connection
230      * failed.
231      * @param handle the client socket handle to finsh to connect
232      * @return true if the socket is connected
233      * @throws Exception any exception thrown by the underlying systems calls
234      */
235     protected abstract boolean finishConnect(H handle) throws Exception;
236     
237     /**
238      * Create a new {@link IoSession} from a connected socket client handle.
239      * Will assign the created {@link IoSession} to the given {@link IoProcessor} for
240      * managing future I/O events.
241      * @param processor the processor in charge of this session
242      * @param handle the newly connected client socket handle
243      * @return a new {@link IoSession}
244      * @throws Exception any exception thrown by the underlying systems calls
245      */
246     protected abstract T newSession(IoProcessor<T> processor, H handle) throws Exception;
247 
248     /**
249      * Close a client socket.
250      * @param handle the client socket
251      * @throws Exception any exception thrown by the underlying systems calls
252      */
253     protected abstract void close(H handle) throws Exception;
254     
255     /**
256      * Interrupt the {@link #select()} method. Used when the poll set need to be modified.
257      */
258     protected abstract void wakeup();
259     
260     /**
261      * Check for connected sockets, interrupt when at least a connection is processed (connected or
262      * failed to connect). All the client socket descriptors processed need to be returned by 
263      * {@link #selectedHandles()}
264      * @return The number of socket having received some data
265      * @throws Exception any exception thrown by the underlying systems calls
266      */
267     protected abstract int select(int timeout) throws Exception;
268     
269     /**
270      * {@link Iterator} for the set of client sockets found connected or 
271      * failed to connect during the last {@link #select()} call.
272      * @return the list of client socket handles to process
273      */
274     protected abstract Iterator<H> selectedHandles();
275     
276     /**
277      * {@link Iterator} for all the client sockets polled for connection.
278      * @return the list of client sockets currently polled for connection
279      */
280     protected abstract Iterator<H> allHandles();
281     
282     /**
283      * Register a new client socket for connection, add it to connection polling
284      * @param handle client socket handle 
285      * @param request the associated {@link ConnectionRequest}
286      * @throws Exception any exception thrown by the underlying systems calls
287      */
288     protected abstract void register(H handle, ConnectionRequest request) throws Exception;
289     
290     /**
291      * get the {@link ConnectionRequest} for a given client socket handle
292      * @param handle the socket client handle 
293      * @return the connection request if the socket is connecting otherwise <code>null</code>
294      */
295     protected abstract ConnectionRequest getConnectionRequest(H handle);
296 
297     /**
298      * {@inheritDoc}
299      */
300     @Override
301     protected final void dispose0() throws Exception {
302         startupWorker();
303         wakeup();
304     }
305 
306     /**
307      * {@inheritDoc}
308      */
309     @Override
310     @SuppressWarnings("unchecked")
311     protected final ConnectFuture connect0(
312             SocketAddress remoteAddress, SocketAddress localAddress,
313             IoSessionInitializer<? extends ConnectFuture> sessionInitializer) {
314         H handle = null;
315         boolean success = false;
316         try {
317             handle = newHandle(localAddress);
318             if (connect(handle, remoteAddress)) {
319                 ConnectFuture future = new DefaultConnectFuture();
320                 T session = newSession(processor, handle);
321                 initSession(session, future, sessionInitializer);
322                 // Forward the remaining process to the IoProcessor.
323                 session.getProcessor().add(session);
324                 success = true;
325                 return future;
326             }
327 
328             success = true;
329         } catch (Exception e) {
330             return DefaultConnectFuture.newFailedFuture(e);
331         } finally {
332             if (!success && handle != null) {
333                 try {
334                     close(handle);
335                 } catch (Exception e) {
336                     ExceptionMonitor.getInstance().exceptionCaught(e);
337                 }
338             }
339         }
340 
341         ConnectionRequest request = new ConnectionRequest(handle, sessionInitializer);
342         connectQueue.add(request);
343         startupWorker();
344         wakeup();
345 
346         return request;
347     }
348 
349     private void startupWorker() {
350         if (!selectable) {
351             connectQueue.clear();
352             cancelQueue.clear();
353         }
354 
355         synchronized (lock) {
356             if (connector == null) {
357                 connector = new Connector();
358                 executeWorker(connector);
359             }
360         }
361     }
362 
363     private int registerNew() {
364         int nHandles = 0;
365         for (; ;) {
366             ConnectionRequest req = connectQueue.poll();
367             if (req == null) {
368                 break;
369             }
370 
371             H handle = req.handle;
372             try {
373                 register(handle, req);
374                 nHandles ++;
375             } catch (Exception e) {
376                 req.setException(e);
377                 try {
378                     close(handle);
379                 } catch (Exception e2) {
380                     ExceptionMonitor.getInstance().exceptionCaught(e2);
381                 }
382             }
383         }
384         return nHandles;
385     }
386 
387     private int cancelKeys() {
388         int nHandles = 0;
389         for (; ;) {
390             ConnectionRequest req = cancelQueue.poll();
391             if (req == null) {
392                 break;
393             }
394 
395             H handle = req.handle;
396             try {
397                 close(handle);
398             } catch (Exception e) {
399                 ExceptionMonitor.getInstance().exceptionCaught(e);
400             } finally {
401                 nHandles ++;
402             }
403         }
404         return nHandles;
405     }
406 
407     /**
408      * Process the incoming connections, creating a new session for each
409      * valid connection. 
410      */
411     private int processConnections(Iterator<H> handlers) {
412         int nHandles = 0;
413         
414         // Loop on each connection request
415         while (handlers.hasNext()) {
416             H handle = handlers.next();
417             handlers.remove();
418 
419             ConnectionRequest connectionRequest = getConnectionRequest(handle);
420             
421             if ( connectionRequest == null) {
422                 continue;
423             }
424             
425             boolean success = false;
426             try {
427                 if (finishConnect(handle)) {
428                     T session = newSession(processor, handle);
429                     initSession(session, connectionRequest, connectionRequest.getSessionInitializer());
430                     // Forward the remaining process to the IoProcessor.
431                     session.getProcessor().add(session);
432                     nHandles ++;
433                 }
434                 success = true;
435             } catch (Throwable e) {
436                 connectionRequest.setException(e);
437             } finally {
438                 if (!success) {
439                     // The connection failed, we have to cancel it.
440                     cancelQueue.offer(connectionRequest);
441                 }
442             }
443         }
444         return nHandles;
445     }
446 
447     private void processTimedOutSessions(Iterator<H> handles) {
448         long currentTime = System.currentTimeMillis();
449 
450         while (handles.hasNext()) {
451             H handle = handles.next();
452             ConnectionRequest connectionRequest = getConnectionRequest(handle);
453 
454             if ((connectionRequest != null) && (currentTime >= connectionRequest.deadline)) {
455                 connectionRequest.setException(
456                         new ConnectException("Connection timed out."));
457                 cancelQueue.offer(connectionRequest);
458             }
459         }
460     }
461 
462     private class Connector implements Runnable {
463 
464         public void run() {
465             int nHandles = 0;
466             while (selectable) {
467                 try {
468                     // the timeout for select shall be smaller of the connect
469                     // timeout or 1 second...
470                     int timeout = (int)Math.min(getConnectTimeoutMillis(), 1000L);
471                     int selected = select(timeout);
472 
473                     nHandles += registerNew();
474 
475                     if (selected > 0) {
476                         nHandles -= processConnections(selectedHandles());
477                     }
478 
479                     processTimedOutSessions(allHandles());
480 
481                     nHandles -= cancelKeys();
482 
483                     if (nHandles == 0) {
484                         synchronized (lock) {
485                             if (connectQueue.isEmpty()) {
486                                 connector = null;
487                                 break;
488                             }
489                         }
490                     }
491                 } catch (Throwable e) {
492                     ExceptionMonitor.getInstance().exceptionCaught(e);
493 
494                     try {
495                         Thread.sleep(1000);
496                     } catch (InterruptedException e1) {
497                         ExceptionMonitor.getInstance().exceptionCaught(e1);
498                     }
499                 }
500             }
501 
502             if (selectable && isDisposing()) {
503                 selectable = false;
504                 try {
505                     if (createdProcessor) {
506                         processor.dispose();
507                     }
508                 } finally {
509                     try {
510                         synchronized (disposalLock) {
511                             if (isDisposing()) {
512                                 destroy();
513                             }
514                         }
515                     } catch (Exception e) {
516                         ExceptionMonitor.getInstance().exceptionCaught(e);
517                     } finally {
518                         disposalFuture.setDone();
519                     }
520                 }
521             }
522         }
523     }
524 
525     public final class ConnectionRequest extends DefaultConnectFuture {
526         private final H handle;
527         private final long deadline;
528         private final IoSessionInitializer<? extends ConnectFuture> sessionInitializer;
529 
530         public ConnectionRequest(H handle, IoSessionInitializer<? extends ConnectFuture> callback) {
531             this.handle = handle;
532             long timeout = getConnectTimeoutMillis();
533             if (timeout <= 0L) {
534                 this.deadline = Long.MAX_VALUE;
535             } else {
536                 this.deadline = System.currentTimeMillis() + timeout;
537             }
538             this.sessionInitializer = callback;
539         }
540 
541         public H getHandle() {
542             return handle;
543         }
544 
545         public long getDeadline() {
546             return deadline;
547         }
548 
549         public IoSessionInitializer<? extends ConnectFuture> getSessionInitializer() {
550             return sessionInitializer;
551         }
552 
553         @Override
554         public void cancel() {
555             if ( !isDone() ) {
556                 super.cancel();
557                 cancelQueue.add(this);
558                 startupWorker();
559                 wakeup();
560             }
561         }
562     }
563 }