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