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.transport.socket.nio;
21  
22  import java.io.IOException;
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.ExceptionMonitor;
31  import org.apache.mina.common.IdleStatus;
32  import org.apache.mina.common.IoFilter.WriteRequest;
33  import org.apache.mina.common.WriteTimeoutException;
34  import org.apache.mina.util.NamePreservingRunnable;
35  import org.apache.mina.util.Queue;
36  import edu.emory.mathcs.backport.java.util.concurrent.Executor;
37  
38  /**
39   * Performs all I/O operations for sockets which is connected or bound. This class is used by MINA internally.
40   *
41   * @author The Apache Directory Project (mina-dev@directory.apache.org)
42   * @version $Rev: 588150 $, $Date: 2007-10-25 15:20:04 +0900 (목, 25 10월 2007) $,
43   */
44  class SocketIoProcessor {
45      private final Object lock = new Object();
46  
47      private final String threadName;
48  
49      private final Executor executor;
50  
51      /**
52       * @noinspection FieldAccessedSynchronizedAndUnsynchronized
53       */
54      private Selector selector;
55  
56      private final Queue newSessions = new Queue();
57  
58      private final Queue removingSessions = new Queue();
59  
60      private final Queue flushingSessions = new Queue();
61  
62      private final Queue trafficControllingSessions = new Queue();
63  
64      private Worker worker;
65  
66      private long lastIdleCheckTime = System.currentTimeMillis();
67  
68      SocketIoProcessor(String threadName, Executor executor) {
69          this.threadName = threadName;
70          this.executor = executor;
71      }
72  
73      void addNew(SocketSessionImpl session) throws IOException {
74          synchronized (newSessions) {
75              newSessions.push(session);
76          }
77  
78          startupWorker();
79      }
80  
81      void remove(SocketSessionImpl session) throws IOException {
82          scheduleRemove(session);
83          startupWorker();
84      }
85  
86      private Selector getSelector() {
87          synchronized (lock) {
88              return this.selector;
89          }
90      }
91      
92      private void startupWorker() throws IOException {
93          synchronized (lock) {
94              if (worker == null) {
95                  selector = Selector.open();
96                  worker = new Worker();
97                  executor.execute(new NamePreservingRunnable(worker, threadName));
98              }
99              selector.wakeup();
100         }
101     }
102 
103     void flush(SocketSessionImpl session) {
104         if (scheduleFlush(session)) {
105             Selector selector = getSelector();
106             if (selector != null) {
107                 selector.wakeup();
108             }
109         }
110     }
111 
112     void updateTrafficMask(SocketSessionImpl session) {
113         scheduleTrafficControl(session);
114         Selector selector = getSelector();
115         if (selector != null) {
116             selector.wakeup();
117         }
118     }
119 
120     private void scheduleRemove(SocketSessionImpl session) {
121         synchronized (removingSessions) {
122             removingSessions.push(session);
123         }
124     }
125 
126     private boolean scheduleFlush(SocketSessionImpl session) {
127         if (session.setScheduledForFlush(true)) {
128             synchronized (flushingSessions) {
129                 flushingSessions.push(session);
130             }
131             
132             return true;
133         }
134         
135         return false;
136     }
137 
138     private void scheduleTrafficControl(SocketSessionImpl session) {
139         synchronized (trafficControllingSessions) {
140             trafficControllingSessions.push(session);
141         }
142     }
143 
144     private void doAddNew() {
145         if (newSessions.isEmpty())
146             return;
147 
148         Selector selector = getSelector();
149         for (;;) {
150             SocketSessionImpl session;
151 
152             synchronized (newSessions) {
153                 session = (SocketSessionImpl) newSessions.pop();
154             }
155 
156             if (session == null)
157                 break;
158 
159             SocketChannel ch = session.getChannel();
160             try {
161                 ch.configureBlocking(false);
162                 session.setSelectionKey(ch.register(selector,
163                         SelectionKey.OP_READ, session));
164 
165                 // AbstractIoFilterChain.CONNECT_FUTURE is cleared inside here
166                 // in AbstractIoFilterChain.fireSessionOpened().
167                 session.getServiceListeners().fireSessionCreated(session);
168             } catch (IOException e) {
169                 // Clear the AbstractIoFilterChain.CONNECT_FUTURE attribute
170                 // and call ConnectFuture.setException().
171                 session.getFilterChain().fireExceptionCaught(session, e);
172             }
173         }
174     }
175 
176     private void doRemove() {
177         if (removingSessions.isEmpty())
178             return;
179 
180         for (;;) {
181             SocketSessionImpl session;
182 
183             synchronized (removingSessions) {
184                 session = (SocketSessionImpl) removingSessions.pop();
185             }
186 
187             if (session == null)
188                 break;
189 
190             SocketChannel ch = session.getChannel();
191             SelectionKey key = session.getSelectionKey();
192             // Retry later if session is not yet fully initialized.
193             // (In case that Session.close() is called before addSession() is processed)
194             if (key == null) {
195                 scheduleRemove(session);
196                 break;
197             }
198             // skip if channel is already closed
199             if (!key.isValid()) {
200                 continue;
201             }
202 
203             try {
204                 key.cancel();
205                 ch.close();
206             } catch (IOException e) {
207                 session.getFilterChain().fireExceptionCaught(session, e);
208             } finally {
209                 releaseWriteBuffers(session);
210                 session.getServiceListeners().fireSessionDestroyed(session);
211             }
212         }
213     }
214 
215     private void process(Set selectedKeys) {
216         Iterator it = selectedKeys.iterator();
217 
218         while (it.hasNext()) {
219             SelectionKey key = (SelectionKey) it.next();
220             SocketSessionImpl session = (SocketSessionImpl) key.attachment();
221 
222             if (key.isReadable() && session.getTrafficMask().isReadable()) {
223                 read(session);
224             }
225 
226             if (key.isWritable() && session.getTrafficMask().isWritable()) {
227                 scheduleFlush(session);
228             }
229         }
230 
231         selectedKeys.clear();
232     }
233 
234     private void read(SocketSessionImpl session) {
235         ByteBuffer buf = ByteBuffer.allocate(session.getReadBufferSize());
236         SocketChannel ch = session.getChannel();
237 
238         try {
239             int readBytes = 0;
240             int ret;
241 
242             try {
243                 while ((ret = ch.read(buf.buf())) > 0) {
244                     readBytes += ret;
245                 }
246             } finally {
247                 buf.flip();
248             }
249 
250             session.increaseReadBytes(readBytes);
251 
252             if (readBytes > 0) {
253                 session.getFilterChain().fireMessageReceived(session, buf);
254                 buf = null;
255                 
256                 if (readBytes * 2 < session.getReadBufferSize()) {
257                     session.decreaseReadBufferSize();
258                 } else if (readBytes == session.getReadBufferSize()) {
259                     session.increaseReadBufferSize();
260                 }
261             }
262             if (ret < 0) {
263                 scheduleRemove(session);
264             }
265         } catch (Throwable e) {
266             if (e instanceof IOException)
267                 scheduleRemove(session);
268             session.getFilterChain().fireExceptionCaught(session, e);
269         } finally {
270             if (buf != null)
271                 buf.release();
272         }
273     }
274 
275     private void notifyIdleness() {
276         // process idle sessions
277         long currentTime = System.currentTimeMillis();
278         if ((currentTime - lastIdleCheckTime) >= 1000) {
279             lastIdleCheckTime = currentTime;
280             Selector selector = getSelector();
281             Set keys = selector.keys();
282             if (keys != null) {
283                 for (Iterator it = keys.iterator(); it.hasNext();) {
284                     SelectionKey key = (SelectionKey) it.next();
285                     SocketSessionImpl session = (SocketSessionImpl) key
286                             .attachment();
287                     notifyIdleness(session, currentTime);
288                 }
289             }
290         }
291     }
292 
293     private void notifyIdleness(SocketSessionImpl session, long currentTime) {
294         notifyIdleness0(session, currentTime, session
295                 .getIdleTimeInMillis(IdleStatus.BOTH_IDLE),
296                 IdleStatus.BOTH_IDLE, Math.max(session.getLastIoTime(), session
297                         .getLastIdleTime(IdleStatus.BOTH_IDLE)));
298         notifyIdleness0(session, currentTime, session
299                 .getIdleTimeInMillis(IdleStatus.READER_IDLE),
300                 IdleStatus.READER_IDLE, Math.max(session.getLastReadTime(),
301                         session.getLastIdleTime(IdleStatus.READER_IDLE)));
302         notifyIdleness0(session, currentTime, session
303                 .getIdleTimeInMillis(IdleStatus.WRITER_IDLE),
304                 IdleStatus.WRITER_IDLE, Math.max(session.getLastWriteTime(),
305                         session.getLastIdleTime(IdleStatus.WRITER_IDLE)));
306 
307         notifyWriteTimeout(session, currentTime, session
308                 .getWriteTimeoutInMillis(), session.getLastWriteTime());
309     }
310 
311     private void notifyIdleness0(SocketSessionImpl session, long currentTime,
312             long idleTime, IdleStatus status, long lastIoTime) {
313         if (idleTime > 0 && lastIoTime != 0
314                 && (currentTime - lastIoTime) >= idleTime) {
315             session.increaseIdleCount(status);
316             session.getFilterChain().fireSessionIdle(session, status);
317         }
318     }
319 
320     private void notifyWriteTimeout(SocketSessionImpl session,
321             long currentTime, long writeTimeout, long lastIoTime) {
322         SelectionKey key = session.getSelectionKey();
323         if (writeTimeout > 0 && (currentTime - lastIoTime) >= writeTimeout
324                 && key != null && key.isValid()
325                 && (key.interestOps() & SelectionKey.OP_WRITE) != 0) {
326             session.getFilterChain().fireExceptionCaught(session,
327                     new WriteTimeoutException());
328         }
329     }
330 
331     private void doFlush() {
332         if (flushingSessions.size() == 0)
333             return;
334 
335         for (;;) {
336             SocketSessionImpl session;
337 
338             synchronized (flushingSessions) {
339                 session = (SocketSessionImpl) flushingSessions.pop();
340             }
341 
342             if (session == null)
343                 break;
344             
345             session.setScheduledForFlush(false);
346 
347             if (!session.isConnected()) {
348                 releaseWriteBuffers(session);
349                 continue;
350             }
351 
352             SelectionKey key = session.getSelectionKey();
353             // Retry later if session is not yet fully initialized.
354             // (In case that Session.write() is called before addSession() is processed)
355             if (key == null) {
356                 scheduleFlush(session);
357                 break;
358             }
359 
360             // Skip if the channel is already closed.
361             if (!key.isValid()) {
362                 continue;
363             }
364 
365             try {
366                 boolean flushedAll = doFlush(session);
367                 if (flushedAll && !session.getWriteRequestQueue().isEmpty() && !session.isScheduledForFlush()) {
368                     scheduleFlush(session);
369                 }
370             } catch (IOException e) {
371                 scheduleRemove(session);
372                 session.getFilterChain().fireExceptionCaught(session, e);
373             }
374         }
375     }
376 
377     private void releaseWriteBuffers(SocketSessionImpl session) {
378         Queue writeRequestQueue = session.getWriteRequestQueue();
379         WriteRequest req;
380 
381         if ((req = (WriteRequest) writeRequestQueue.pop()) != null) {
382             ByteBuffer buf = (ByteBuffer) req.getMessage();
383             try {
384                 buf.release();
385             } catch (IllegalStateException e) {
386                 session.getFilterChain().fireExceptionCaught(session, e);
387             } finally {
388                 // The first unwritten empty buffer must be
389                 // forwarded to the filter chain.
390                 if (buf.hasRemaining()) {
391                     req.getFuture().setWritten(false);
392                 } else {
393                     session.getFilterChain().fireMessageSent(session, req);                    
394                 }
395             }
396 
397             // Discard others.
398             while ((req = (WriteRequest) writeRequestQueue.pop()) != null) {
399                 try {
400                     ((ByteBuffer) req.getMessage()).release();
401                 } catch (IllegalStateException e) {
402                     session.getFilterChain().fireExceptionCaught(session, e);
403                 } finally {
404                     req.getFuture().setWritten(false);
405                 }
406             }
407         }
408     }
409 
410     private boolean doFlush(SocketSessionImpl session) throws IOException {
411         // Clear OP_WRITE
412         SelectionKey key = session.getSelectionKey();
413         key.interestOps(key.interestOps() & (~SelectionKey.OP_WRITE));
414 
415         SocketChannel ch = session.getChannel();
416         Queue writeRequestQueue = session.getWriteRequestQueue();
417 
418         int writtenBytes = 0;
419         int maxWrittenBytes = ((SocketSessionConfig) session.getConfig()).getSendBufferSize() << 1;
420         try {
421             for (;;) {
422                 WriteRequest req;
423     
424                 synchronized (writeRequestQueue) {
425                     req = (WriteRequest) writeRequestQueue.first();
426                 }
427     
428                 if (req == null)
429                     break;
430     
431                 ByteBuffer buf = (ByteBuffer) req.getMessage();
432                 if (buf.remaining() == 0) {
433                     synchronized (writeRequestQueue) {
434                         writeRequestQueue.pop();
435                     }
436     
437                     buf.reset();
438                     
439                     if (!buf.hasRemaining()) {
440                         session.increaseWrittenMessages();
441                     }
442                     
443                     session.getFilterChain().fireMessageSent(session, req);
444                     continue;
445                 }
446     
447                 if (key.isWritable()) {
448                     writtenBytes += ch.write(buf.buf());
449                 }
450     
451                 if (buf.hasRemaining() || writtenBytes >= maxWrittenBytes) {
452                     // Kernel buffer is full or wrote too much.
453                     key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
454                     return false;
455                 }
456             }
457         } finally {
458             session.increaseWrittenBytes(writtenBytes);
459         }
460         
461         return true;
462     }
463 
464     private void doUpdateTrafficMask() {
465         if (trafficControllingSessions.isEmpty())
466             return;
467 
468         for (;;) {
469             SocketSessionImpl session;
470 
471             synchronized (trafficControllingSessions) {
472                 session = (SocketSessionImpl) trafficControllingSessions.pop();
473             }
474 
475             if (session == null)
476                 break;
477 
478             SelectionKey key = session.getSelectionKey();
479             // Retry later if session is not yet fully initialized.
480             // (In case that Session.suspend??() or session.resume??() is 
481             // called before addSession() is processed)
482             if (key == null) {
483                 scheduleTrafficControl(session);
484                 break;
485             }
486             // skip if channel is already closed
487             if (!key.isValid()) {
488                 continue;
489             }
490 
491             // The normal is OP_READ and, if there are write requests in the
492             // session's write queue, set OP_WRITE to trigger flushing.
493             int ops = SelectionKey.OP_READ;
494             Queue writeRequestQueue = session.getWriteRequestQueue();
495             synchronized (writeRequestQueue) {
496                 if (!writeRequestQueue.isEmpty()) {
497                     ops |= SelectionKey.OP_WRITE;
498                 }
499             }
500 
501             // Now mask the preferred ops with the mask of the current session
502             int mask = session.getTrafficMask().getInterestOps();
503             key.interestOps(ops & mask);
504         }
505     }
506 
507     private class Worker implements Runnable {
508         public void run() {
509             Selector selector = getSelector();
510             for (;;) {
511                 try {
512                     int nKeys = selector.select(1000);
513                     doAddNew();
514                     doUpdateTrafficMask();
515 
516                     if (nKeys > 0) {
517                         process(selector.selectedKeys());
518                     }
519 
520                     doFlush();
521                     doRemove();
522                     notifyIdleness();
523 
524                     if (selector.keys().isEmpty()) {
525                         synchronized (lock) {
526                             if (selector.keys().isEmpty()
527                                     && newSessions.isEmpty()) {
528                                 worker = null;
529 
530                                 try {
531                                     selector.close();
532                                 } catch (IOException e) {
533                                     ExceptionMonitor.getInstance()
534                                             .exceptionCaught(e);
535                                 } finally {
536                                     selector = null;
537                                 }
538 
539                                 break;
540                             }
541                         }
542                     }
543                 } catch (Throwable t) {
544                     ExceptionMonitor.getInstance().exceptionCaught(t);
545 
546                     try {
547                         Thread.sleep(1000);
548                     } catch (InterruptedException e1) {
549                         ExceptionMonitor.getInstance().exceptionCaught(e1);
550                     }
551                 }
552             }
553         }
554     }
555 }