View Javadoc

1   /*
2    *   @(#) $Id: SocketIoProcessor.java 327113 2005-10-21 06:59:15Z trustin $
3    *
4    *   Copyright 2004 The Apache Software Foundation
5    *
6    *   Licensed under the Apache License, Version 2.0 (the "License");
7    *   you may not use this file except in compliance with the License.
8    *   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, software
13   *   distributed under the License is distributed on an "AS IS" BASIS,
14   *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   *   See the License for the specific language governing permissions and
16   *   limitations under the License.
17   *
18   */
19  package org.apache.mina.io.socket;
20  
21  import java.io.IOException;
22  import java.nio.channels.CancelledKeyException;
23  import java.nio.channels.SelectionKey;
24  import java.nio.channels.Selector;
25  import java.nio.channels.SocketChannel;
26  import java.util.Iterator;
27  import java.util.Set;
28  
29  import org.apache.mina.common.ByteBuffer;
30  import org.apache.mina.common.IdleStatus;
31  import org.apache.mina.common.SessionConfig;
32  import org.apache.mina.io.WriteTimeoutException;
33  import org.apache.mina.util.Queue;
34  
35  /***
36   * Performs all I/O operations for sockets which is connected or bound.
37   * This class is used by MINA internally.
38   * 
39   * @author The Apache Directory Project (dev@directory.apache.org)
40   * @version $Rev: 327113 $, $Date: 2005-10-21 15:59:15 +0900 $,
41   */
42  class SocketIoProcessor
43  {
44      private static final SocketIoProcessor instance;
45  
46      static
47      {
48          SocketIoProcessor tmp;
49  
50          try
51          {
52              tmp = new SocketIoProcessor();
53          }
54          catch( IOException e )
55          {
56              InternalError error = new InternalError(
57                                                       "Failed to open selector." );
58              error.initCause( e );
59              throw error;
60          }
61  
62          instance = tmp;
63      }
64  
65      private final Selector selector;
66  
67      private final Queue newSessions = new Queue();
68  
69      private final Queue removingSessions = new Queue();
70  
71      private final Queue flushingSessions = new Queue();
72  
73      private final Queue readableSessions = new Queue();
74  
75      private Worker worker;
76  
77      private long lastIdleCheckTime = System.currentTimeMillis();
78  
79      private SocketIoProcessor() throws IOException
80      {
81          selector = Selector.open();
82      }
83  
84      static SocketIoProcessor getInstance()
85      {
86          return instance;
87      }
88  
89      void addSession( SocketSession session )
90      {
91          synchronized( this )
92          {
93              synchronized( newSessions )
94              {
95                  newSessions.push( session );
96              }
97              startupWorker();
98          }
99  
100         selector.wakeup();
101     }
102 
103     void removeSession( SocketSession session )
104     {
105         scheduleRemove( session );
106         startupWorker();
107         selector.wakeup();
108     }
109 
110     private synchronized void startupWorker()
111     {
112         if( worker == null )
113         {
114             worker = new Worker();
115             worker.start();
116         }
117     }
118 
119     void flushSession( SocketSession session )
120     {
121         scheduleFlush( session );
122         selector.wakeup();
123     }
124 
125     void addReadableSession( SocketSession session )
126     {
127         synchronized( readableSessions )
128         {
129             readableSessions.push( session );
130         }
131         selector.wakeup();
132     }
133 
134     private void addSessions()
135     {
136         if( newSessions.isEmpty() )
137             return;
138 
139         SocketSession session;
140 
141         for( ;; )
142         {
143             synchronized( newSessions )
144             {
145                 session = ( SocketSession ) newSessions.pop();
146             }
147 
148             if( session == null )
149                 break;
150 
151             SocketChannel ch = session.getChannel();
152             boolean registered;
153 
154             try
155             {
156                 ch.configureBlocking( false );
157                 session.setSelectionKey( ch.register( selector,
158                                                       SelectionKey.OP_READ,
159                                                       session ) );
160                 registered = true;
161             }
162             catch( IOException e )
163             {
164                 registered = false;
165                 session.getManagerFilterChain().exceptionCaught( session, e );
166             }
167 
168             if( registered )
169             {
170                 session.getManagerFilterChain().sessionOpened( session );
171             }
172         }
173     }
174 
175     private void removeSessions()
176     {
177         if( removingSessions.isEmpty() )
178             return;
179 
180         for( ;; )
181         {
182             SocketSession session;
183 
184             synchronized( removingSessions )
185             {
186                 session = ( SocketSession ) removingSessions.pop();
187             }
188 
189             if( session == null )
190                 break;
191 
192             SocketChannel ch = session.getChannel();
193             SelectionKey key = session.getSelectionKey();
194             // Retry later if session is not yet fully initialized.
195             // (In case that Session.close() is called before addSession() is processed)
196             if( key == null )
197             {
198                 scheduleRemove( session );
199                 break;
200             }
201 
202             // skip if channel is already closed
203             if( !key.isValid() )
204             {
205                 continue;
206             }
207 
208             try
209             {
210                 key.cancel();
211                 ch.close();
212             }
213             catch( IOException e )
214             {
215                 session.getManagerFilterChain().exceptionCaught( session, e );
216             }
217             finally
218             {
219                 releaseWriteBuffers( session );
220 
221                 session.getManagerFilterChain().sessionClosed( session );
222                 session.notifyClose();
223             }
224         }
225     }
226 
227     private void processSessions( Set selectedKeys )
228     {
229         Iterator it = selectedKeys.iterator();
230 
231         while( it.hasNext() )
232         {
233             SelectionKey key = ( SelectionKey ) it.next();
234             SocketSession session = ( SocketSession ) key.attachment();
235 
236             if( key.isReadable() )
237             {
238                 read( session );
239             }
240 
241             if( key.isWritable() )
242             {
243                 scheduleFlush( session );
244             }
245         }
246 
247         selectedKeys.clear();
248     }
249 
250     private void read( SocketSession session )
251     {
252         ByteBuffer buf = ByteBuffer.allocate(
253                 (( SocketSessionConfig ) session.getConfig()).getSessionReceiveBufferSize() ); 
254         SocketChannel ch = session.getChannel();
255 
256         try
257         {
258             int readBytes = 0;
259             int ret;
260 
261             buf.clear();
262 
263             try
264             {
265                 while( ( ret = ch.read( buf.buf() ) ) > 0 )
266                 {
267                     readBytes += ret;
268                 }
269             }
270             finally
271             {
272                 buf.flip();
273             }
274 
275             session.increaseReadBytes( readBytes );
276             session.resetIdleCount( IdleStatus.BOTH_IDLE );
277             session.resetIdleCount( IdleStatus.READER_IDLE );
278 
279             if( readBytes > 0 )
280             {
281                 ByteBuffer newBuf = ByteBuffer.allocate( readBytes );
282                 newBuf.put( buf );
283                 newBuf.flip();
284                 session.getManagerFilterChain().dataRead( session, newBuf );
285             }
286             if( ret < 0 )
287             {
288                 scheduleRemove( session );
289             }
290         }
291         catch( Throwable e )
292         {
293             if( e instanceof IOException )
294                 scheduleRemove( session );
295             session.getManagerFilterChain().exceptionCaught( session, e );
296         }
297         finally
298         {
299             buf.release();
300         }
301     }
302 
303     private void scheduleRemove( SocketSession session )
304     {
305         synchronized( removingSessions )
306         {
307             removingSessions.push( session );
308         }
309     }
310 
311     private void scheduleFlush( SocketSession session )
312     {
313         synchronized( flushingSessions )
314         {
315             flushingSessions.push( session );
316         }
317     }
318 
319     private void notifyIdleSessions()
320     {
321         Set keys = selector.keys();
322         Iterator it;
323         SocketSession session;
324 
325         // process idle sessions
326         long currentTime = System.currentTimeMillis();
327 
328         if( ( keys != null ) && ( ( currentTime - lastIdleCheckTime ) >= 1000 ) )
329         {
330             lastIdleCheckTime = currentTime;
331             it = keys.iterator();
332 
333             while( it.hasNext() )
334             {
335                 SelectionKey key = ( SelectionKey ) it.next();
336                 session = ( SocketSession ) key.attachment();
337 
338                 notifyIdleSession( session, currentTime );
339             }
340         }
341     }
342 
343     private void notifyIdleSession( SocketSession session, long currentTime )
344     {
345         SessionConfig config = session.getConfig();
346 
347         notifyIdleSession0(
348                 session, currentTime,
349                 config.getIdleTimeInMillis( IdleStatus.BOTH_IDLE ),
350                 IdleStatus.BOTH_IDLE,
351                 Math.max( session.getLastIoTime(), session.getLastIdleTime( IdleStatus.BOTH_IDLE ) ) );
352         notifyIdleSession0(
353                 session, currentTime,
354                 config.getIdleTimeInMillis( IdleStatus.READER_IDLE ),
355                 IdleStatus.READER_IDLE,
356                 Math.max( session.getLastReadTime(), session.getLastIdleTime( IdleStatus.READER_IDLE ) ) );
357         notifyIdleSession0(
358                 session, currentTime,
359                 config.getIdleTimeInMillis( IdleStatus.WRITER_IDLE ),
360                 IdleStatus.WRITER_IDLE,
361                 Math.max( session.getLastWriteTime(), session.getLastIdleTime( IdleStatus.WRITER_IDLE ) ) );
362 
363         notifyWriteTimeoutSession( session, currentTime, config
364                 .getWriteTimeoutInMillis(), session.getLastWriteTime() );
365     }
366 
367     private void notifyIdleSession0( SocketSession session, long currentTime,
368                                     long idleTime, IdleStatus status,
369                                     long lastIoTime )
370     {
371         if( idleTime > 0 && lastIoTime != 0
372             && ( currentTime - lastIoTime ) >= idleTime )
373         {
374             session.increaseIdleCount( status );
375             session.getManagerFilterChain().sessionIdle( session, status );
376         }
377     }
378 
379     private void notifyWriteTimeoutSession( SocketSession session,
380                                            long currentTime,
381                                            long writeTimeout, long lastIoTime )
382     {
383         if( writeTimeout > 0
384             && ( currentTime - lastIoTime ) >= writeTimeout
385             && session.getSelectionKey() != null
386             && ( session.getSelectionKey().interestOps() & SelectionKey.OP_WRITE ) != 0 )
387         {
388             session
389                     .getManagerFilterChain()
390                     .exceptionCaught( session, new WriteTimeoutException() );
391         }
392     }
393 
394     private void flushSessions()
395     {
396         if( flushingSessions.size() == 0 )
397             return;
398 
399         for( ;; )
400         {
401             SocketSession session;
402 
403             synchronized( flushingSessions )
404             {
405                 session = ( SocketSession ) flushingSessions.pop();
406             }
407 
408             if( session == null )
409                 break;
410 
411             if( !session.isConnected() )
412             {
413                 releaseWriteBuffers( session );
414                 continue;
415             }
416 
417             // If encountered write request before session is initialized, 
418             // (In case that Session.write() is called before addSession() is processed)
419             if( session.getSelectionKey() == null )
420             {
421                 // Reschedule for later write
422                 scheduleFlush( session );
423                 break;
424             }
425             else
426             {
427                 try
428                 {
429                     flush( session );
430                 }
431                 catch( CancelledKeyException e )
432                 {
433                     // Connection is closed unexpectedly.
434                     scheduleRemove( session );
435                 }
436                 catch( IOException e )
437                 {
438                     scheduleRemove( session );
439                     session.getManagerFilterChain().exceptionCaught( session, e );
440                 }
441             }
442         }
443     }
444     
445     private void releaseWriteBuffers( SocketSession session )
446     {
447         Queue writeBufferQueue = session.getWriteBufferQueue();
448         session.getWriteMarkerQueue().clear();
449         ByteBuffer buf;
450         
451         while( ( buf = (ByteBuffer) writeBufferQueue.pop() ) != null )
452         {
453             try
454             {
455                 buf.release();
456             }
457             catch( IllegalStateException e )
458             {
459                 session.getManagerFilterChain().exceptionCaught( session, e );
460             }
461         }
462     }
463 
464     private void flush( SocketSession session ) throws IOException
465     {
466         SocketChannel ch = session.getChannel();
467 
468         Queue writeBufferQueue = session.getWriteBufferQueue();
469         Queue writeMarkerQueue = session.getWriteMarkerQueue();
470 
471         ByteBuffer buf;
472         Object marker;
473         for( ;; )
474         {
475             synchronized( writeBufferQueue )
476             {
477                 buf = ( ByteBuffer ) writeBufferQueue.first();
478                 marker = writeMarkerQueue.first();
479             }
480 
481             if( buf == null )
482                 break;
483 
484             if( buf.remaining() == 0 )
485             {
486                 synchronized( writeBufferQueue )
487                 {
488                     writeBufferQueue.pop();
489                     writeMarkerQueue.pop();
490                 }
491                 try
492                 {
493                     buf.release();
494                 }
495                 catch( IllegalStateException e )
496                 {
497                     session.getManagerFilterChain().exceptionCaught( session, e );
498                 }
499 
500                 session.increaseWrittenWriteRequests();
501                 session.getManagerFilterChain().dataWritten( session, marker );
502                 continue;
503             }
504 
505             int writtenBytes = 0;
506             try
507             {
508                 writtenBytes = ch.write( buf.buf() );
509             }
510             finally
511             {
512                 if( writtenBytes > 0 )
513                 {
514                     session.increaseWrittenBytes( writtenBytes );
515                     session.resetIdleCount( IdleStatus.BOTH_IDLE );
516                     session.resetIdleCount( IdleStatus.WRITER_IDLE );
517                 }
518 
519                 SelectionKey key = session.getSelectionKey();
520                 if( buf.hasRemaining() )
521                 {
522                     // Kernel buffer is full
523                     key
524                             .interestOps( key.interestOps()
525                                           | SelectionKey.OP_WRITE );
526                     break;
527                 }
528                 else
529                 {
530                     key.interestOps( key.interestOps()
531                                      & ( ~SelectionKey.OP_WRITE ) );
532                 }
533             }
534         }
535     }
536 
537     private class Worker extends Thread
538     {
539         public Worker()
540         {
541             super( "SocketIoProcessor" );
542         }
543 
544         public void run()
545         {
546             for( ;; )
547             {
548                 try
549                 {
550                     int nKeys = selector.select( 1000 );
551                     addSessions();
552 
553                     if( nKeys > 0 )
554                     {
555                         processSessions( selector.selectedKeys() );
556                     }
557 
558                     flushSessions();
559                     removeSessions();
560                     notifyIdleSessions();
561 
562                     if( selector.keys().isEmpty() )
563                     {
564                         synchronized( SocketIoProcessor.this )
565                         {
566                             if( selector.keys().isEmpty() &&
567                                 newSessions.isEmpty() )
568                             {
569                                 worker = null;
570                                 break;
571                             }
572                         }
573                     }
574                 }
575                 catch( IOException e )
576                 {
577                     e.printStackTrace();
578 
579                     try
580                     {
581                         Thread.sleep( 1000 );
582                     }
583                     catch( InterruptedException e1 )
584                     {
585                     }
586                 }
587             }
588         }
589     }
590 }