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 }