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 | } |