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