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  import java.io.IOException;
23  import java.util.ArrayList;
24  import java.util.HashMap;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Queue;
29  import java.util.concurrent.ConcurrentLinkedQueue;
30  import java.util.concurrent.Executor;
31  import java.util.concurrent.atomic.AtomicInteger;
32  
33  import org.apache.mina.core.buffer.IoBuffer;
34  import org.apache.mina.core.file.FileRegion;
35  import org.apache.mina.core.filterchain.IoFilterChain;
36  import org.apache.mina.core.future.DefaultIoFuture;
37  import org.apache.mina.core.service.AbstractIoService;
38  import org.apache.mina.core.service.IoProcessor;
39  import org.apache.mina.core.session.AbstractIoSession;
40  import org.apache.mina.core.session.IoSession;
41  import org.apache.mina.core.session.IoSessionConfig;
42  import org.apache.mina.core.write.WriteRequest;
43  import org.apache.mina.core.write.WriteRequestQueue;
44  import org.apache.mina.core.write.WriteToClosedSessionException;
45  import org.apache.mina.util.ExceptionMonitor;
46  import org.apache.mina.util.NamePreservingRunnable;
47  
48  /**
49   * An abstract implementation of {@link IoProcessor} which helps
50   * transport developers to write an {@link IoProcessor} easily.
51   * This class is in charge of active polling a set of {@link IoSession}
52   * and trigger events when some I/O operation is possible.
53   *
54   * @author The Apache MINA Project (dev@mina.apache.org)
55   * @version $Rev: 713957 $, $Date: 2008-11-14 10:27:16 +0100 (Fri, 14 Nov 2008) $
56   */
57  public abstract class AbstractPollingIoProcessor<T extends AbstractIoSession> implements IoProcessor<T> {
58      /**
59       * The maximum loop count for a write operation until
60       * {@link #write(AbstractIoSession, IoBuffer, int)} returns non-zero value.
61       * It is similar to what a spin lock is for in concurrency programming.
62       * It improves memory utilization and write throughput significantly.
63       */
64      private static final int WRITE_SPIN_COUNT = 256;
65  
66      /** A map containing the last Thread ID for each class */
67      private static final Map<Class<?>, AtomicInteger> threadIds = 
68          new HashMap<Class<?>, AtomicInteger>();
69  
70      private final Object lock = new Object();
71      private final String threadName;
72      private final Executor executor;
73  
74      /** A Session queue containing the newly created sessions */
75      private final Queue<T> newSessions = new ConcurrentLinkedQueue<T>();
76      private final Queue<T> removingSessions = new ConcurrentLinkedQueue<T>();
77      private final Queue<T> flushingSessions = new ConcurrentLinkedQueue<T>();
78      private final Queue<T> trafficControllingSessions = new ConcurrentLinkedQueue<T>();
79  
80      /** The processor thread : it handles the incoming messages */
81      private Processor processor;
82      
83      private long lastIdleCheckTime;
84  
85      private final Object disposalLock = new Object();
86      private volatile boolean disposing;
87      private volatile boolean disposed;
88      private final DefaultIoFuture disposalFuture = new DefaultIoFuture(null);
89  
90      /**
91       * Create an {@link AbstractPollingIoProcessor} with the given {@link Executor}
92       * for handling I/Os events.
93       * 
94       * @param executor the {@link Executor} for handling I/O events
95       */
96      protected AbstractPollingIoProcessor(Executor executor) {
97          if (executor == null) {
98              throw new NullPointerException("executor");
99          }
100 
101         this.threadName = nextThreadName();
102         this.executor = executor;
103     }
104 
105     /**
106      * Compute the thread ID for this class instance. As we may have different
107      * classes, we store the last ID number into a Map associating the class
108      * name to the last assigned ID.
109      *   
110      * @return a name for the current thread, based on the class name and
111      * an incremental value, starting at 1. 
112      */
113     private String nextThreadName() {
114         Class<?> cls = getClass();
115         int newThreadId;
116         
117         // We synchronize this block to avoid a concurrent access to 
118         // the actomicInteger (it can be modified by another thread, while
119         // being seen as null by another thread)
120         synchronized( threadIds ) {
121             // Get the current ID associated to this class' name
122             AtomicInteger threadId = threadIds.get(cls);
123             
124             if (threadId == null) {
125                 // We never have seen this class before, just create a
126                 // new ID starting at 1 for it, and associate this ID
127                 // with the class name in the map.
128                 newThreadId = 1;
129                 threadIds.put(cls, new AtomicInteger(newThreadId));
130             } else {
131                 // Just increment the lat ID, and get it.
132                 newThreadId = threadId.incrementAndGet();
133             }
134         }
135         
136         // Now we can compute the name for this thread
137         return cls.getSimpleName() + '-' + newThreadId;
138     }
139 
140     /**
141      * {@inheritDoc}
142      */
143     public final boolean isDisposing() {
144         return disposing;
145     }
146 
147     /**
148      * {@inheritDoc}
149      */
150     public final boolean isDisposed() {
151         return disposed;
152     }
153     
154     /**
155      * {@inheritDoc}
156      */
157     public final void dispose() {
158         if (disposed) {
159             return;
160         }
161 
162         synchronized (disposalLock) {
163             if (!disposing) {
164                 disposing = true;
165                 startupWorker();
166             }
167         }
168 
169         disposalFuture.awaitUninterruptibly();
170         disposed = true;
171     }
172 
173     /**
174      * Dispose the resources used by this {@link IoProcessor} for polling 
175      * the client connections
176      * @throws Exception if some low level IO error occurs
177      */
178     protected abstract void dispose0() throws Exception;
179 
180     /**
181      * poll those sessions for the given timeout
182      * @param timeout milliseconds before the call timeout if no event appear
183      * @return The number of session ready for read or for write
184      * @throws Exception if some low level IO error occurs
185      */
186     protected abstract int select(int timeout) throws Exception;
187     
188     /**
189      * poll those sessions forever
190      * @return The number of session ready for read or for write
191      * @throws Exception if some low level IO error occurs
192      */
193     protected abstract int select() throws Exception;
194     
195     /**
196      * Say if the list of {@link IoSession} polled by this {@link IoProcessor} 
197      * is empty
198      * @return true if at least a session is managed by this {@link IoProcessor}
199      */
200     protected abstract boolean isSelectorEmpty();
201     
202     /**
203      * Interrupt the {@link AbstractPollingIoProcessor#select(int) call.
204      */
205     protected abstract void wakeup();
206     
207     /**
208      * Get an {@link Iterator} for the list of {@link IoSession} polled by this
209      * {@link IoProcessor}   
210      * @return {@link Iterator} of {@link IoSession}
211      */
212     protected abstract Iterator<T> allSessions();
213     
214     /**
215      * Get an {@link Iterator} for the list of {@link IoSession} found selected 
216      * by the last call of {@link AbstractPollingIoProcessor#select(int)
217      * @return {@link Iterator} of {@link IoSession} read for I/Os operation
218      */
219     protected abstract Iterator<T> selectedSessions();
220     
221     /**
222      * Get the sate of a session (preparing, open, closed)
223      * @param session the {@link IoSession} to inspect
224      * @return the state of the session
225      */
226     protected abstract SessionState state(T session);
227 
228     /**
229      * Is the session ready for writing
230      * @param session the session queried
231      * @return true is ready, false if not ready
232      */
233     protected abstract boolean isWritable(T session);
234 
235     /**
236      * Is the session ready for reading
237      * @param session the session queried
238      * @return true is ready, false if not ready
239      */
240     protected abstract boolean isReadable(T session);
241 
242     /**
243      * register a session for writing
244      * @param session the session registered
245      * @param interested true for registering, false for removing
246      */
247     protected abstract void setInterestedInWrite(T session, boolean interested)
248             throws Exception;
249 
250     /**
251      * register a session for reading
252      * @param session the session registered
253      * @param interested true for registering, false for removing
254      */
255     protected abstract void setInterestedInRead(T session, boolean interested)
256             throws Exception;
257 
258     /**
259      * is this session registered for reading
260      * @param session the session queried
261      * @return true is registered for reading
262      */
263     protected abstract boolean isInterestedInRead(T session);
264 
265     /**
266      * is this session registered for writing
267      * @param session the session queried
268      * @return true is registered for writing
269      */
270     protected abstract boolean isInterestedInWrite(T session);
271 
272     /**
273      * Initialize the polling of a session. Add it to the polling process. 
274      * @param session the {@link IoSession} to add to the polling
275      * @throws Exception any exception thrown by the underlying system calls
276      */
277     protected abstract void init(T session) throws Exception;
278     
279     /**
280      * Destroy the underlying client socket handle
281      * @param session the {@link IoSession}
282      * @throws Exception any exception thrown by the underlying system calls
283      */
284     protected abstract void destroy(T session) throws Exception;
285     
286     /**
287      * Reads a sequence of bytes from a {@link IoSession} into the given {@link IoBuffer}. 
288      * Is called when the session was found ready for reading.
289      * @param session the session to read
290      * @param buf the buffer to fill
291      * @return the number of bytes read
292      * @throws Exception any exception thrown by the underlying system calls
293      */
294     protected abstract int read(T session, IoBuffer buf) throws Exception;
295 
296     /**
297      * Write a sequence of bytes to a {@link IoSession}, means to be called when a session
298      * was found ready for writing.
299      * @param session the session to write
300      * @param buf the buffer to write
301      * @param length the number of bytes to write can be superior to the number of bytes remaining
302      * in the buffer
303      * @return the number of byte written
304      * @throws Exception any exception thrown by the underlying system calls
305      */
306     protected abstract int write(T session, IoBuffer buf, int length) throws Exception;
307     
308     /**
309      * Write a part of a file to a {@link IoSession}, if the underlying API isn't supporting
310      * system calls like sendfile(), you can throw a {@link UnsupportedOperationException} so 
311      * the file will be send using usual {@link #write(AbstractIoSession, IoBuffer, int)} call. 
312      * @param session the session to write
313      * @param region the file region to write
314      * @param length the length of the portion to send
315      * @return the number of written bytes
316      * @throws Exception any exception thrown by the underlying system calls
317      */
318     protected abstract int transferFile(T session, FileRegion region, int length) throws Exception;
319 
320     /**
321      * {@inheritDoc}
322      */
323     public final void add(T session) {
324         if (isDisposing()) {
325             throw new IllegalStateException("Already disposed.");
326         }
327 
328         // Adds the session to the newSession queue and starts the worker
329         newSessions.add(session);
330         startupWorker();
331     }
332 
333     /**
334      * {@inheritDoc}
335      */
336     public final void remove(T session) {
337         scheduleRemove(session);
338         startupWorker();
339     }
340 
341     private void scheduleRemove(T session) {
342         removingSessions.add(session);
343     }
344 
345     /**
346      * {@inheritDoc}
347      */
348     public final void flush(T session) {
349         boolean needsWakeup = flushingSessions.isEmpty();
350         if (scheduleFlush(session) && needsWakeup) {
351             wakeup();
352         }
353     }
354 
355     private boolean scheduleFlush(T session) {
356         if (session.setScheduledForFlush(true)) {
357             flushingSessions.add(session);
358             return true;
359         }
360         return false;
361     }
362 
363     /**
364      * {@inheritDoc}
365      */
366     public final void updateTrafficMask(T session) {
367         scheduleTrafficControl(session);
368         wakeup();
369     }
370 
371     private void scheduleTrafficControl(T session) {
372         trafficControllingSessions.add(session);
373     }
374 
375     private void startupWorker() {
376         synchronized (lock) {
377             if (processor == null) {
378                 processor = new Processor();
379                 executor.execute(new NamePreservingRunnable(processor, threadName));
380             }
381         }
382         wakeup();
383     }
384 
385     private int add() {
386         int addedSessions = 0;
387         
388         // Loop on the new sessions blocking queue, to count
389         // the number of sessions who has been created
390         for (;;) {
391             T session = newSessions.poll();
392 
393             if (session == null) {
394                 // We don't have anymore new sessions
395                 break;
396             }
397 
398 
399             if (addNow(session)) {
400                 // The new session has been added to the 
401                 addedSessions ++;
402             }
403         }
404 
405         return addedSessions;
406     }
407 
408     private boolean addNow(T session) {
409 
410         boolean registered = false;
411         boolean notified = false;
412         try {
413             init(session);
414             registered = true;
415 
416             // Build the filter chain of this session.
417             session.getService().getFilterChainBuilder().buildFilterChain(
418                     session.getFilterChain());
419 
420             // DefaultIoFilterChain.CONNECT_FUTURE is cleared inside here
421             // in AbstractIoFilterChain.fireSessionOpened().
422             ((AbstractIoService) session.getService()).getListeners().fireSessionCreated(session);
423             notified = true;
424         } catch (Throwable e) {
425             if (notified) {
426                 // Clear the DefaultIoFilterChain.CONNECT_FUTURE attribute
427                 // and call ConnectFuture.setException().
428                 scheduleRemove(session);
429                 IoFilterChain filterChain = session.getFilterChain(); 
430                 filterChain.fireExceptionCaught(e);
431                 wakeup();
432             } else {
433                 ExceptionMonitor.getInstance().exceptionCaught(e);
434                 try {
435                     destroy(session);
436                 } catch (Exception e1) {
437                     ExceptionMonitor.getInstance().exceptionCaught(e1);
438                 } finally {
439                     registered = false;
440                 }
441             }
442         }
443         return registered;
444     }
445 
446     private int remove() {
447         int removedSessions = 0;
448         for (; ;) {
449             T session = removingSessions.poll();
450 
451             if (session == null) {
452                 break;
453             }
454 
455             SessionState state = state(session);
456             switch (state) {
457             case OPEN:
458                 if (removeNow(session)) {
459                     removedSessions ++;
460                 }
461                 break;
462             case CLOSED:
463                 // Skip if channel is already closed
464                 break;
465             case PREPARING:
466                 // Retry later if session is not yet fully initialized.
467                 // (In case that Session.close() is called before addSession() is processed)
468                 scheduleRemove(session);
469                 return removedSessions;
470             default:
471                 throw new IllegalStateException(String.valueOf(state));
472             }
473         }
474 
475         return removedSessions;
476     }
477 
478     private boolean removeNow(T session) {
479         clearWriteRequestQueue(session);
480 
481         try {
482             destroy(session);
483             return true;
484         } catch (Exception e) {
485             IoFilterChain filterChain = session.getFilterChain(); 
486             filterChain.fireExceptionCaught(e);
487         } finally {
488             clearWriteRequestQueue(session);
489             ((AbstractIoService) session.getService()).getListeners().fireSessionDestroyed(session);
490         }
491         return false;
492     }
493 
494     private void clearWriteRequestQueue(T session) {
495         WriteRequestQueue writeRequestQueue = session.getWriteRequestQueue();
496         WriteRequest req;
497 
498         List<WriteRequest> failedRequests = new ArrayList<WriteRequest>();
499 
500         if ((req = writeRequestQueue.poll(session)) != null) {
501             Object m = req.getMessage();
502             if (m instanceof IoBuffer) {
503                 IoBuffer buf = (IoBuffer) req.getMessage();
504 
505                 // The first unwritten empty buffer must be
506                 // forwarded to the filter chain.
507                 if (buf.hasRemaining()) {
508                     buf.reset();
509                     failedRequests.add(req);
510                 } else {
511                     IoFilterChain filterChain = session.getFilterChain(); 
512                     filterChain.fireMessageSent(req);
513                 }
514             } else {
515                 failedRequests.add(req);
516             }
517 
518             // Discard others.
519             while ((req = writeRequestQueue.poll(session)) != null) {
520                 failedRequests.add(req);
521             }
522         }
523 
524         // Create an exception and notify.
525         if (!failedRequests.isEmpty()) {
526             WriteToClosedSessionException cause = new WriteToClosedSessionException(failedRequests);
527             for (WriteRequest r: failedRequests) {
528                 session.decreaseScheduledBytesAndMessages(r);
529                 r.getFuture().setException(cause);
530             }
531             IoFilterChain filterChain = session.getFilterChain(); 
532             filterChain.fireExceptionCaught(cause);
533         }
534     }
535 
536     private void process() throws Exception {
537         for (Iterator<T> i = selectedSessions(); i.hasNext();) {
538         	T session = i.next();
539             process(session);
540             i.remove();
541         }
542     }
543 
544     private void process(T session) {
545 
546         if (isReadable(session) && !session.isReadSuspended()) {
547             read(session);
548         }
549 
550         if (isWritable(session) && !session.isWriteSuspended()) {
551             scheduleFlush(session);
552         }
553     }
554 
555     private void read(T session) {
556         IoSessionConfig config = session.getConfig();
557         IoBuffer buf = IoBuffer.allocate(config.getReadBufferSize());
558 
559         final boolean hasFragmentation =
560             session.getTransportMetadata().hasFragmentation();
561 
562         try {
563             int readBytes = 0;
564             int ret;
565 
566             try {
567                 if (hasFragmentation) {
568                     while ((ret = read(session, buf)) > 0) {
569                         readBytes += ret;
570                         if (!buf.hasRemaining()) {
571                             break;
572                         }
573                     }
574                 } else {
575                     ret = read(session, buf);
576                     if (ret > 0) {
577                         readBytes = ret;
578                     }
579                 }
580             } finally {
581                 buf.flip();
582             }
583 
584             if (readBytes > 0) {
585                 IoFilterChain filterChain = session.getFilterChain(); 
586                 filterChain.fireMessageReceived(buf);
587                 buf = null;
588 
589                 if (hasFragmentation) {
590                     if (readBytes << 1 < config.getReadBufferSize()) {
591                         session.decreaseReadBufferSize();
592                     } else if (readBytes == config.getReadBufferSize()) {
593                         session.increaseReadBufferSize();
594                     }
595                 }
596             }
597             if (ret < 0) {
598                 scheduleRemove(session);
599             }
600         } catch (Throwable e) {
601             if (e instanceof IOException) {
602                 scheduleRemove(session);
603             }
604             IoFilterChain filterChain = session.getFilterChain(); 
605             filterChain.fireExceptionCaught(e);
606         }
607     }
608 
609     private void notifyIdleSessions(long currentTime) throws Exception {
610         // process idle sessions
611         if (currentTime - lastIdleCheckTime >= 1000) {
612             lastIdleCheckTime = currentTime;
613             AbstractIoSession.notifyIdleness(allSessions(), currentTime);
614         }
615     }
616 
617     private void flush(long currentTime) {
618         final T firstSession = flushingSessions.peek();
619         if (firstSession == null) {
620             return;
621         }
622 
623         T session = flushingSessions.poll(); // the same one with firstSession
624         for (; ;) {
625             session.setScheduledForFlush(false);
626             SessionState state = state(session);
627             switch (state) {
628             case OPEN:
629                 try {
630                     boolean flushedAll = flushNow(session, currentTime);
631                     if (flushedAll && !session.getWriteRequestQueue().isEmpty(session) &&
632                         !session.isScheduledForFlush()) {
633                         scheduleFlush(session);
634                     }
635                 } catch (Exception e) {
636                     scheduleRemove(session);
637                     IoFilterChain filterChain = session.getFilterChain(); 
638                     filterChain.fireExceptionCaught(e);
639                 }
640                 break;
641             case CLOSED:
642                 // Skip if the channel is already closed.
643                 break;
644             case PREPARING:
645                 // Retry later if session is not yet fully initialized.
646                 // (In case that Session.write() is called before addSession() is processed)
647                 scheduleFlush(session);
648                 return;
649             default:
650                 throw new IllegalStateException(String.valueOf(state));
651             }
652 
653             session = flushingSessions.peek();
654             if (session == null || session == firstSession) {
655                 break;
656             }
657             session = flushingSessions.poll();
658         }
659     }
660 
661     private boolean flushNow(T session, long currentTime) {
662         if (!session.isConnected()) {
663             scheduleRemove(session);
664             return false;
665         }
666 
667         final boolean hasFragmentation =
668             session.getTransportMetadata().hasFragmentation();
669 
670         final WriteRequestQueue writeRequestQueue = session.getWriteRequestQueue();
671 
672         // Set limitation for the number of written bytes for read-write
673         // fairness.  I used maxReadBufferSize * 3 / 2, which yields best
674         // performance in my experience while not breaking fairness much.
675         final int maxWrittenBytes = session.getConfig().getMaxReadBufferSize() +
676                               (session.getConfig().getMaxReadBufferSize() >>> 1);
677         int writtenBytes = 0;
678         try {
679             // Clear OP_WRITE
680             setInterestedInWrite(session, false);
681             do {
682                 // Check for pending writes.
683                 WriteRequest req = session.getCurrentWriteRequest();
684                 if (req == null) {
685                     req = writeRequestQueue.poll(session);
686                     if (req == null) {
687                         break;
688                     }
689                     session.setCurrentWriteRequest(req);
690                 }
691 
692                 int localWrittenBytes = 0;
693                 Object message = req.getMessage();
694                 if (message instanceof IoBuffer) {
695                     localWrittenBytes = writeBuffer(
696                             session, req, hasFragmentation,
697                             maxWrittenBytes - writtenBytes,
698                             currentTime);
699                     if (localWrittenBytes > 0 && ((IoBuffer)message).hasRemaining() ) {
700                     	// the buffer isn't empty, we re-interest it in writing 
701                     	writtenBytes += localWrittenBytes;    	
702                     	setInterestedInWrite(session, true);
703                         return false;
704                     }
705                 } else if (message instanceof FileRegion) {
706                     localWrittenBytes = writeFile(
707                             session, req, hasFragmentation,
708                             maxWrittenBytes - writtenBytes,
709                             currentTime);
710 
711                     // Fix for Java bug on Linux http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5103988
712                     // If there's still data to be written in the FileRegion, return 0 indicating that we need
713                     // to pause until writing may resume.
714                     if (localWrittenBytes > 0 && ((FileRegion) message).getRemainingBytes() > 0) {
715                         writtenBytes += localWrittenBytes;
716                         setInterestedInWrite(session, true);
717                         return false;
718                     }
719                 } else {
720                     throw new IllegalStateException("Don't know how to handle message of type '" + message.getClass().getName() + "'.  Are you missing a protocol encoder?");
721                 }
722 
723                 if (localWrittenBytes == 0) {
724                     // Kernel buffer is full.
725                     setInterestedInWrite(session, true);
726                     return false;
727                 }
728 
729                 writtenBytes += localWrittenBytes;
730 
731                 if (writtenBytes >= maxWrittenBytes) {
732                     // Wrote too much
733                     scheduleFlush(session);
734                     return false;
735                 }
736             } while (writtenBytes < maxWrittenBytes);
737         } catch (Exception e) {
738             IoFilterChain filterChain = session.getFilterChain(); 
739             filterChain.fireExceptionCaught(e);
740             return false;
741         }
742 
743         return true;
744     }
745 
746     private int writeBuffer(T session, WriteRequest req,
747             boolean hasFragmentation, int maxLength, long currentTime) throws Exception {
748         IoBuffer buf = (IoBuffer) req.getMessage();
749         int localWrittenBytes = 0;
750         if (buf.hasRemaining()) {
751             int length;
752             if (hasFragmentation) {
753                 length = Math.min(buf.remaining(), maxLength);
754             } else {
755                 length = buf.remaining();
756             }
757             for (int i = WRITE_SPIN_COUNT; i > 0; i --) {
758                 localWrittenBytes = write(session, buf, length);
759                 if (localWrittenBytes != 0) {
760                     break;
761                 }
762             }
763         }
764 
765         session.increaseWrittenBytes(localWrittenBytes, currentTime);
766 
767         if (!buf.hasRemaining() ||
768                 !hasFragmentation && localWrittenBytes != 0) {
769             // Buffer has been sent, clear the current request.
770             buf.reset();
771             fireMessageSent(session, req);
772         }
773         return localWrittenBytes;
774     }
775 
776     private int writeFile(T session, WriteRequest req,
777             boolean hasFragmentation, int maxLength, long currentTime) throws Exception {
778         int localWrittenBytes;
779         FileRegion region = (FileRegion) req.getMessage();
780         if (region.getRemainingBytes() > 0) {
781             int length;
782             if (hasFragmentation) {
783                 length = (int) Math.min(region.getRemainingBytes(), maxLength);
784             } else {
785                 length = (int) Math.min(Integer.MAX_VALUE, region.getRemainingBytes());
786             }
787             localWrittenBytes = transferFile(session, region, length);
788             region.update(localWrittenBytes);
789         } else {
790             localWrittenBytes = 0;
791         }
792 
793         session.increaseWrittenBytes(localWrittenBytes, currentTime);
794 
795         if (region.getRemainingBytes() <= 0 ||
796                     !hasFragmentation && localWrittenBytes != 0) {
797             fireMessageSent(session, req);
798         }
799 
800         return localWrittenBytes;
801     }
802 
803     private void fireMessageSent(T session, WriteRequest req) {
804         session.setCurrentWriteRequest(null);
805         IoFilterChain filterChain = session.getFilterChain(); 
806         filterChain.fireMessageSent(req);
807     }
808 
809     private void updateTrafficMask() {
810         for (; ;) {
811             T session = trafficControllingSessions.poll();
812 
813             if (session == null) {
814                 break;
815             }
816 
817             SessionState state = state(session);
818             switch (state) {
819             case OPEN:
820             	updateTrafficControl(session);
821                 break;
822             case CLOSED:
823                 break;
824             case PREPARING:
825                 // Retry later if session is not yet fully initialized.
826                 // (In case that Session.suspend??() or session.resume??() is
827                 // called before addSession() is processed)
828                 scheduleTrafficControl(session);
829                 return;
830             default:
831                 throw new IllegalStateException(String.valueOf(state));
832             }
833         }
834     }
835 
836     public void updateTrafficControl(T session) {
837     	try {
838             setInterestedInRead(session, !session.isReadSuspended());
839         } catch (Exception e) {
840             IoFilterChain filterChain = session.getFilterChain(); 
841             filterChain.fireExceptionCaught(e);
842         }
843         try {
844             setInterestedInWrite(
845                     session,
846                     !session.getWriteRequestQueue().isEmpty(session) &&
847                             !session.isWriteSuspended());
848         } catch (Exception e) {
849             IoFilterChain filterChain = session.getFilterChain(); 
850             filterChain.fireExceptionCaught(e);
851         }
852     }
853         
854     private class Processor implements Runnable {
855         public void run() {
856             int nSessions = 0;
857             lastIdleCheckTime = System.currentTimeMillis();
858 
859             for (;;) {
860                 try {
861                     int selected = select(1000);
862 
863                     nSessions += add();
864                     updateTrafficMask();
865 
866                     if (selected > 0) {
867                         process();
868                     }
869 
870                     long currentTime = System.currentTimeMillis();
871                     flush(currentTime);
872                     nSessions -= remove();
873                     notifyIdleSessions(currentTime);
874 
875                     if (nSessions == 0) {
876                         synchronized (lock) {
877                             if (newSessions.isEmpty() && isSelectorEmpty()) {
878                                 processor = null;
879                                 break;
880                             }
881                         }
882                     }
883 
884                     // Disconnect all sessions immediately if disposal has been
885                     // requested so that we exit this loop eventually.
886                     if (isDisposing()) {
887                         for (Iterator<T> i = allSessions(); i.hasNext(); ) {
888                             scheduleRemove(i.next());
889                         }
890                         wakeup();
891                     }
892                 } catch (Throwable t) {
893                     ExceptionMonitor.getInstance().exceptionCaught(t);
894 
895                     try {
896                         Thread.sleep(1000);
897                     } catch (InterruptedException e1) {
898                         ExceptionMonitor.getInstance().exceptionCaught(e1);
899                     }
900                 }
901             }
902 
903             try {
904                 synchronized (disposalLock) {
905                     if (isDisposing()) {
906                         dispose0();
907                     }
908                 }
909             } catch (Throwable t) {
910                 ExceptionMonitor.getInstance().exceptionCaught(t);
911             } finally {
912                 disposalFuture.setValue(true);
913             }
914         }
915     }
916 
917     protected static enum SessionState {
918         OPEN,
919         CLOSED,
920         PREPARING,
921     }
922 }