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  
23  import java.net.SocketAddress;
24  import java.nio.channels.ClosedSelectorException;
25  import java.nio.channels.SelectionKey;
26  import java.util.Collections;
27  import java.util.HashMap;
28  import java.util.HashSet;
29  import java.util.Iterator;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Queue;
33  import java.util.Set;
34  import java.util.concurrent.ConcurrentLinkedQueue;
35  import java.util.concurrent.Executor;
36  import java.util.concurrent.Semaphore;
37  
38  import org.apache.mina.core.RuntimeIoException;
39  import org.apache.mina.core.buffer.IoBuffer;
40  import org.apache.mina.core.service.AbstractIoAcceptor;
41  import org.apache.mina.core.service.IoAcceptor;
42  import org.apache.mina.core.service.IoProcessor;
43  import org.apache.mina.core.session.AbstractIoSession;
44  import org.apache.mina.core.session.ExpiringSessionRecycler;
45  import org.apache.mina.core.session.IoSession;
46  import org.apache.mina.core.session.IoSessionConfig;
47  import org.apache.mina.core.session.IoSessionRecycler;
48  import org.apache.mina.core.write.WriteRequest;
49  import org.apache.mina.core.write.WriteRequestQueue;
50  import org.apache.mina.util.ExceptionMonitor;
51  
52  
53  /**
54   * {@link IoAcceptor} for datagram transport (UDP/IP).
55   *
56   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
57   * @org.apache.xbean.XBean
58   *
59    * @param <S> the type of the {@link IoSession} this processor can handle
60  */
61  public abstract class AbstractPollingConnectionlessIoAcceptor<S extends AbstractIoSession, H> extends
62      AbstractIoAcceptor implements IoProcessor<S>
63  {
64  
65      private static final IoSessionRecycler DEFAULT_RECYCLER = new ExpiringSessionRecycler();
66  
67      /**
68       * A timeout used for the select, as we need to get out to deal with idle
69       * sessions
70       */
71      private static final long SELECT_TIMEOUT = 1000L;
72  
73      /** A lock used to protect the selector to be waked up before it's created */
74      private final Semaphore lock = new Semaphore( 1 );
75  
76      private final Queue<AcceptorOperationFuture> registerQueue = new ConcurrentLinkedQueue<AcceptorOperationFuture>();
77  
78      private final Queue<AcceptorOperationFuture> cancelQueue = new ConcurrentLinkedQueue<AcceptorOperationFuture>();
79  
80      private final Queue<S> flushingSessions = new ConcurrentLinkedQueue<S>();
81  
82      private final Map<SocketAddress, H> boundHandles = Collections.synchronizedMap( new HashMap<SocketAddress, H>() );
83  
84      private IoSessionRecycler sessionRecycler = DEFAULT_RECYCLER;
85  
86      private final ServiceOperationFuture disposalFuture = new ServiceOperationFuture();
87  
88      private volatile boolean selectable;
89  
90      /** The thread responsible of accepting incoming requests */
91      private Acceptor acceptor;
92  
93      private long lastIdleCheckTime;
94  
95  
96      /**
97       * Creates a new instance.
98       */
99      protected AbstractPollingConnectionlessIoAcceptor( IoSessionConfig sessionConfig )
100     {
101         this( sessionConfig, null );
102     }
103 
104 
105     /**
106      * Creates a new instance.
107      */
108     protected AbstractPollingConnectionlessIoAcceptor( IoSessionConfig sessionConfig, Executor executor )
109     {
110         super( sessionConfig, executor );
111 
112         try
113         {
114             init();
115             selectable = true;
116         }
117         catch ( RuntimeException e )
118         {
119             throw e;
120         }
121         catch ( Exception e )
122         {
123             throw new RuntimeIoException( "Failed to initialize.", e );
124         }
125         finally
126         {
127             if ( !selectable )
128             {
129                 try
130                 {
131                     destroy();
132                 }
133                 catch ( Exception e )
134                 {
135                     ExceptionMonitor.getInstance().exceptionCaught( e );
136                 }
137             }
138         }
139     }
140 
141 
142     protected abstract void init() throws Exception;
143 
144 
145     protected abstract void destroy() throws Exception;
146 
147 
148     protected abstract int select() throws Exception;
149 
150 
151     protected abstract int select( long timeout ) throws Exception;
152 
153 
154     protected abstract void wakeup();
155 
156 
157     protected abstract Set<SelectionKey> selectedHandles();
158 
159 
160     protected abstract H open( SocketAddress localAddress ) throws Exception;
161 
162 
163     protected abstract void close( H handle ) throws Exception;
164 
165 
166     protected abstract SocketAddress localAddress( H handle ) throws Exception;
167 
168 
169     protected abstract boolean isReadable( H handle );
170 
171 
172     protected abstract boolean isWritable( H handle );
173 
174 
175     protected abstract SocketAddress receive( H handle, IoBuffer buffer ) throws Exception;
176 
177 
178     protected abstract int send( S session, IoBuffer buffer, SocketAddress remoteAddress ) throws Exception;
179 
180 
181     protected abstract S newSession( IoProcessor<S> processor, H handle, SocketAddress remoteAddress ) throws Exception;
182 
183 
184     protected abstract void setInterestedInWrite( S session, boolean interested ) throws Exception;
185 
186 
187     /**
188      * {@inheritDoc}
189      */
190     @Override
191     protected void dispose0() throws Exception
192     {
193         unbind();
194         startupAcceptor();
195         wakeup();
196     }
197 
198 
199     /**
200      * {@inheritDoc}
201      */
202     @Override
203     protected final Set<SocketAddress> bindInternal( List<? extends SocketAddress> localAddresses ) throws Exception
204     {
205         // Create a bind request as a Future operation. When the selector
206         // have handled the registration, it will signal this future.
207         AcceptorOperationFuture request = new AcceptorOperationFuture( localAddresses );
208 
209         // adds the Registration request to the queue for the Workers
210         // to handle
211         registerQueue.add( request );
212 
213         // creates the Acceptor instance and has the local
214         // executor kick it off.
215         startupAcceptor();
216 
217         // As we just started the acceptor, we have to unblock the select()
218         // in order to process the bind request we just have added to the
219         // registerQueue.
220         try
221         {
222             lock.acquire();
223 
224             // Wait a bit to give a chance to the Acceptor thread to do the select()
225             Thread.sleep( 10 );
226             wakeup();
227         }
228         finally
229         {
230             lock.release();
231         }
232 
233         // Now, we wait until this request is completed.
234         request.awaitUninterruptibly();
235 
236         if ( request.getException() != null )
237         {
238             throw request.getException();
239         }
240 
241         // Update the local addresses.
242         // setLocalAddresses() shouldn't be called from the worker thread
243         // because of deadlock.
244         Set<SocketAddress> newLocalAddresses = new HashSet<SocketAddress>();
245 
246         for ( H handle : boundHandles.values() )
247         {
248             newLocalAddresses.add( localAddress( handle ) );
249         }
250 
251         return newLocalAddresses;
252     }
253 
254 
255     /**
256      * {@inheritDoc}
257      */
258     @Override
259     protected final void unbind0( List<? extends SocketAddress> localAddresses ) throws Exception
260     {
261         AcceptorOperationFuture request = new AcceptorOperationFuture( localAddresses );
262 
263         cancelQueue.add( request );
264         startupAcceptor();
265         wakeup();
266 
267         request.awaitUninterruptibly();
268 
269         if ( request.getException() != null )
270         {
271             throw request.getException();
272         }
273     }
274 
275 
276     /**
277      * {@inheritDoc}
278      */
279     public final IoSession newSession( SocketAddress remoteAddress, SocketAddress localAddress )
280     {
281         if ( isDisposing() )
282         {
283             throw new IllegalStateException( "Already disposed." );
284         }
285 
286         if ( remoteAddress == null )
287         {
288             throw new IllegalArgumentException( "remoteAddress" );
289         }
290 
291         synchronized ( bindLock )
292         {
293             if ( !isActive() )
294             {
295                 throw new IllegalStateException( "Can't create a session from a unbound service." );
296             }
297 
298             try
299             {
300                 return newSessionWithoutLock( remoteAddress, localAddress );
301             }
302             catch ( RuntimeException e )
303             {
304                 throw e;
305             }
306             catch ( Error e )
307             {
308                 throw e;
309             }
310             catch ( Exception e )
311             {
312                 throw new RuntimeIoException( "Failed to create a session.", e );
313             }
314         }
315     }
316 
317 
318     private IoSession newSessionWithoutLock( SocketAddress remoteAddress, SocketAddress localAddress ) throws Exception
319     {
320         H handle = boundHandles.get( localAddress );
321 
322         if ( handle == null )
323         {
324             throw new IllegalArgumentException( "Unknown local address: " + localAddress );
325         }
326 
327         IoSession session;
328 
329         synchronized ( sessionRecycler )
330         {
331             session = sessionRecycler.recycle( remoteAddress );
332 
333             if ( session != null )
334             {
335                 return session;
336             }
337 
338             // If a new session needs to be created.
339             S newSession = newSession( this, handle, remoteAddress );
340             getSessionRecycler().put( newSession );
341             session = newSession;
342         }
343 
344         initSession( session, null, null );
345 
346         try
347         {
348             this.getFilterChainBuilder().buildFilterChain( session.getFilterChain() );
349             getListeners().fireSessionCreated( session );
350         }
351         catch ( Throwable t )
352         {
353             ExceptionMonitor.getInstance().exceptionCaught( t );
354         }
355 
356         return session;
357     }
358 
359 
360     public final IoSessionRecycler getSessionRecycler()
361     {
362         return sessionRecycler;
363     }
364 
365 
366     public final void setSessionRecycler( IoSessionRecycler sessionRecycler )
367     {
368         synchronized ( bindLock )
369         {
370             if ( isActive() )
371             {
372                 throw new IllegalStateException( "sessionRecycler can't be set while the acceptor is bound." );
373             }
374 
375             if ( sessionRecycler == null )
376             {
377                 sessionRecycler = DEFAULT_RECYCLER;
378             }
379 
380             this.sessionRecycler = sessionRecycler;
381         }
382     }
383 
384 
385     /**
386      * {@inheritDoc}
387      */
388     public void add( S session )
389     {
390         // Nothing to do for UDP
391     }
392 
393 
394     /**
395      * {@inheritDoc}
396      */
397     public void flush( S session )
398     {
399         if ( scheduleFlush( session ) )
400         {
401             wakeup();
402         }
403     }
404 
405 
406     /**
407      * {@inheritDoc}
408      */
409     public void write( S session, WriteRequest writeRequest )
410     {
411         // We will try to write the message directly
412         long currentTime = System.currentTimeMillis();
413         final WriteRequestQueue writeRequestQueue = session.getWriteRequestQueue();
414         final int maxWrittenBytes = session.getConfig().getMaxReadBufferSize()
415             + ( session.getConfig().getMaxReadBufferSize() >>> 1 );
416 
417         int writtenBytes = 0;
418 
419         // Deal with the special case of a Message marker (no bytes in the request)
420         // We just have to return after having calle dthe messageSent event
421         IoBuffer buf = ( IoBuffer ) writeRequest.getMessage();
422 
423         if ( buf.remaining() == 0 )
424         {
425             // Clear and fire event
426             session.setCurrentWriteRequest( null );
427             buf.reset();
428             session.getFilterChain().fireMessageSent( writeRequest );
429             return;
430         }
431 
432         // Now, write the data
433         try
434         {
435             for ( ;; )
436             {
437                 if ( writeRequest == null )
438                 {
439                     writeRequest = writeRequestQueue.poll( session );
440 
441                     if ( writeRequest == null )
442                     {
443                         setInterestedInWrite( session, false );
444                         break;
445                     }
446 
447                     session.setCurrentWriteRequest( writeRequest );
448                 }
449 
450                 buf = ( IoBuffer ) writeRequest.getMessage();
451 
452                 if ( buf.remaining() == 0 )
453                 {
454                     // Clear and fire event
455                     session.setCurrentWriteRequest( null );
456                     buf.reset();
457                     session.getFilterChain().fireMessageSent( writeRequest );
458                     continue;
459                 }
460 
461                 SocketAddress destination = writeRequest.getDestination();
462 
463                 if ( destination == null )
464                 {
465                     destination = session.getRemoteAddress();
466                 }
467 
468                 int localWrittenBytes = send( session, buf, destination );
469 
470                 if ( ( localWrittenBytes == 0 ) || ( writtenBytes >= maxWrittenBytes ) )
471                 {
472                     // Kernel buffer is full or wrote too much
473                     setInterestedInWrite( session, true );
474 
475                     session.getWriteRequestQueue().offer( session, writeRequest );
476                     scheduleFlush( session );
477                 }
478                 else
479                 {
480                     setInterestedInWrite( session, false );
481 
482                     // Clear and fire event
483                     session.setCurrentWriteRequest( null );
484                     writtenBytes += localWrittenBytes;
485                     buf.reset();
486                     session.getFilterChain().fireMessageSent( writeRequest );
487 
488                     break;
489                 }
490             }
491         }
492         catch ( Exception e )
493         {
494             session.getFilterChain().fireExceptionCaught( e );
495         }
496         finally
497         {
498             session.increaseWrittenBytes( writtenBytes, currentTime );
499         }
500     }
501 
502 
503     /**
504      * {@inheritDoc}
505      */
506     public void remove( S session )
507     {
508         getSessionRecycler().remove( session );
509         getListeners().fireSessionDestroyed( session );
510     }
511 
512 
513     /**
514      * {@inheritDoc}
515      */
516     public void updateTrafficControl( S session )
517     {
518         throw new UnsupportedOperationException();
519     }
520 
521 
522     /**
523      * Starts the inner Acceptor thread.
524      */
525     private void startupAcceptor() throws InterruptedException
526     {
527         if ( !selectable )
528         {
529             registerQueue.clear();
530             cancelQueue.clear();
531             flushingSessions.clear();
532         }
533 
534         lock.acquire();
535 
536         if ( acceptor == null )
537         {
538             acceptor = new Acceptor();
539             executeWorker( acceptor );
540         }
541         else
542         {
543             lock.release();
544         }
545     }
546 
547 
548     private boolean scheduleFlush( S session )
549     {
550         // Set the schedule for flush flag if the session
551         // has not already be added to the flushingSessions
552         // queue
553         if ( session.setScheduledForFlush( true ) )
554         {
555             flushingSessions.add( session );
556             return true;
557         }
558         else
559         {
560             return false;
561         }
562     }
563 
564     /**
565      * This private class is used to accept incoming connection from
566      * clients. It's an infinite loop, which can be stopped when all
567      * the registered handles have been removed (unbound).
568      */
569     private class Acceptor implements Runnable
570     {
571         public void run()
572         {
573             int nHandles = 0;
574             lastIdleCheckTime = System.currentTimeMillis();
575 
576             // Release the lock
577             lock.release();
578 
579             while ( selectable )
580             {
581                 try
582                 {
583                     int selected = select( SELECT_TIMEOUT );
584 
585                     nHandles += registerHandles();
586 
587                     if ( nHandles == 0 )
588                     {
589                         try
590                         {
591                             lock.acquire();
592 
593                             if ( registerQueue.isEmpty() && cancelQueue.isEmpty() )
594                             {
595                                 acceptor = null;
596                                 break;
597                             }
598                         }
599                         finally
600                         {
601                             lock.release();
602                         }
603                     }
604 
605                     if ( selected > 0 )
606                     {
607                         processReadySessions( selectedHandles() );
608                     }
609 
610                     long currentTime = System.currentTimeMillis();
611                     flushSessions( currentTime );
612                     nHandles -= unregisterHandles();
613 
614                     notifyIdleSessions( currentTime );
615                 }
616                 catch ( ClosedSelectorException cse )
617                 {
618                     // If the selector has been closed, we can exit the loop
619                     break;
620                 }
621                 catch ( Exception e )
622                 {
623                     ExceptionMonitor.getInstance().exceptionCaught( e );
624 
625                     try
626                     {
627                         Thread.sleep( 1000 );
628                     }
629                     catch ( InterruptedException e1 )
630                     {
631                     }
632                 }
633             }
634 
635             if ( selectable && isDisposing() )
636             {
637                 selectable = false;
638                 try
639                 {
640                     destroy();
641                 }
642                 catch ( Exception e )
643                 {
644                     ExceptionMonitor.getInstance().exceptionCaught( e );
645                 }
646                 finally
647                 {
648                     disposalFuture.setValue( true );
649                 }
650             }
651         }
652     }
653 
654 
655     @SuppressWarnings("unchecked")
656     private void processReadySessions( Set<SelectionKey> handles )
657     {
658         Iterator<SelectionKey> iterator = handles.iterator();
659 
660         while ( iterator.hasNext() )
661         {
662             SelectionKey key = iterator.next();
663             H handle = ( H ) key.channel();
664             iterator.remove();
665 
666             try
667             {
668                 if ( ( key != null ) && key.isValid() && key.isReadable() )
669                 {
670                     readHandle( handle );
671                 }
672 
673                 if ( ( key != null ) && key.isValid() && key.isWritable() )
674                 {
675                     for ( IoSession session : getManagedSessions().values() )
676                     {
677                         scheduleFlush( ( S ) session );
678                     }
679                 }
680             }
681             catch ( Throwable t )
682             {
683                 ExceptionMonitor.getInstance().exceptionCaught( t );
684             }
685         }
686     }
687 
688 
689     private void readHandle( H handle ) throws Exception
690     {
691         IoBuffer readBuf = IoBuffer.allocate( getSessionConfig().getReadBufferSize() );
692 
693         SocketAddress remoteAddress = receive( handle, readBuf );
694 
695         if ( remoteAddress != null )
696         {
697             IoSession session = newSessionWithoutLock( remoteAddress, localAddress( handle ) );
698 
699             readBuf.flip();
700 
701             session.getFilterChain().fireMessageReceived( readBuf );
702         }
703     }
704 
705 
706     private void flushSessions( long currentTime )
707     {
708         for ( ;; )
709         {
710             S session = flushingSessions.poll();
711 
712             if ( session == null )
713             {
714                 break;
715             }
716 
717             // Reset the Schedule for flush flag for this session,
718             // as we are flushing it now
719             session.unscheduledForFlush();
720 
721             try
722             {
723                 boolean flushedAll = flush( session, currentTime );
724                 if ( flushedAll && !session.getWriteRequestQueue().isEmpty( session ) && !session.isScheduledForFlush() )
725                 {
726                     scheduleFlush( session );
727                 }
728             }
729             catch ( Exception e )
730             {
731                 session.getFilterChain().fireExceptionCaught( e );
732             }
733         }
734     }
735 
736 
737     private boolean flush( S session, long currentTime ) throws Exception
738     {
739         final WriteRequestQueue writeRequestQueue = session.getWriteRequestQueue();
740         final int maxWrittenBytes = session.getConfig().getMaxReadBufferSize()
741             + ( session.getConfig().getMaxReadBufferSize() >>> 1 );
742 
743         int writtenBytes = 0;
744 
745         try
746         {
747             for ( ;; )
748             {
749                 WriteRequest req = session.getCurrentWriteRequest();
750 
751                 if ( req == null )
752                 {
753                     req = writeRequestQueue.poll( session );
754 
755                     if ( req == null )
756                     {
757                         setInterestedInWrite( session, false );
758                         break;
759                     }
760 
761                     session.setCurrentWriteRequest( req );
762                 }
763 
764                 IoBuffer buf = ( IoBuffer ) req.getMessage();
765 
766                 if ( buf.remaining() == 0 )
767                 {
768                     // Clear and fire event
769                     session.setCurrentWriteRequest( null );
770                     buf.reset();
771                     session.getFilterChain().fireMessageSent( req );
772                     continue;
773                 }
774 
775                 SocketAddress destination = req.getDestination();
776 
777                 if ( destination == null )
778                 {
779                     destination = session.getRemoteAddress();
780                 }
781 
782                 int localWrittenBytes = send( session, buf, destination );
783 
784                 if ( ( localWrittenBytes == 0 ) || ( writtenBytes >= maxWrittenBytes ) )
785                 {
786                     // Kernel buffer is full or wrote too much
787                     setInterestedInWrite( session, true );
788 
789                     return false;
790                 }
791                 else
792                 {
793                     setInterestedInWrite( session, false );
794 
795                     // Clear and fire event
796                     session.setCurrentWriteRequest( null );
797                     writtenBytes += localWrittenBytes;
798                     buf.reset();
799                     session.getFilterChain().fireMessageSent( req );
800                 }
801             }
802         }
803         finally
804         {
805             session.increaseWrittenBytes( writtenBytes, currentTime );
806         }
807 
808         return true;
809     }
810 
811 
812     private int registerHandles()
813     {
814         for ( ;; )
815         {
816             AcceptorOperationFuture req = registerQueue.poll();
817 
818             if ( req == null )
819             {
820                 break;
821             }
822 
823             Map<SocketAddress, H> newHandles = new HashMap<SocketAddress, H>();
824             List<SocketAddress> localAddresses = req.getLocalAddresses();
825 
826             try
827             {
828                 for ( SocketAddress socketAddress : localAddresses )
829                 {
830                     H handle = open( socketAddress );
831                     newHandles.put( localAddress( handle ), handle );
832                 }
833 
834                 boundHandles.putAll( newHandles );
835 
836                 getListeners().fireServiceActivated();
837                 req.setDone();
838 
839                 return newHandles.size();
840             }
841             catch ( Exception e )
842             {
843                 req.setException( e );
844             }
845             finally
846             {
847                 // Roll back if failed to bind all addresses.
848                 if ( req.getException() != null )
849                 {
850                     for ( H handle : newHandles.values() )
851                     {
852                         try
853                         {
854                             close( handle );
855                         }
856                         catch ( Exception e )
857                         {
858                             ExceptionMonitor.getInstance().exceptionCaught( e );
859                         }
860                     }
861 
862                     wakeup();
863                 }
864             }
865         }
866 
867         return 0;
868     }
869 
870 
871     private int unregisterHandles()
872     {
873         int nHandles = 0;
874 
875         for ( ;; )
876         {
877             AcceptorOperationFuture request = cancelQueue.poll();
878             if ( request == null )
879             {
880                 break;
881             }
882 
883             // close the channels
884             for ( SocketAddress socketAddress : request.getLocalAddresses() )
885             {
886                 H handle = boundHandles.remove( socketAddress );
887 
888                 if ( handle == null )
889                 {
890                     continue;
891                 }
892 
893                 try
894                 {
895                     close( handle );
896                     wakeup(); // wake up again to trigger thread death
897                 }
898                 catch ( Throwable e )
899                 {
900                     ExceptionMonitor.getInstance().exceptionCaught( e );
901                 }
902                 finally
903                 {
904                     nHandles++;
905                 }
906             }
907 
908             request.setDone();
909         }
910 
911         return nHandles;
912     }
913 
914 
915     private void notifyIdleSessions( long currentTime )
916     {
917         // process idle sessions
918         if ( currentTime - lastIdleCheckTime >= 1000 )
919         {
920             lastIdleCheckTime = currentTime;
921             AbstractIoSession.notifyIdleness( getListeners().getManagedSessions().values().iterator(), currentTime );
922         }
923     }
924 }