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.net.PortUnreachableException;
24  import java.util.ArrayList;
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.ConcurrentHashMap;
30  import java.util.concurrent.ConcurrentLinkedQueue;
31  import java.util.concurrent.Executor;
32  import java.util.concurrent.atomic.AtomicBoolean;
33  import java.util.concurrent.atomic.AtomicInteger;
34  
35  import org.apache.mina.core.buffer.IoBuffer;
36  import org.apache.mina.core.file.FileRegion;
37  import org.apache.mina.core.filterchain.IoFilterChain;
38  import org.apache.mina.core.filterchain.IoFilterChainBuilder;
39  import org.apache.mina.core.future.DefaultIoFuture;
40  import org.apache.mina.core.service.AbstractIoService;
41  import org.apache.mina.core.service.IoProcessor;
42  import org.apache.mina.core.service.IoServiceListenerSupport;
43  import org.apache.mina.core.session.AbstractIoSession;
44  import org.apache.mina.core.session.IoSession;
45  import org.apache.mina.core.session.IoSessionConfig;
46  import org.apache.mina.core.session.SessionState;
47  import org.apache.mina.core.write.WriteRequest;
48  import org.apache.mina.core.write.WriteRequestQueue;
49  import org.apache.mina.core.write.WriteToClosedSessionException;
50  import org.apache.mina.transport.socket.AbstractDatagramSessionConfig;
51  import org.apache.mina.util.ExceptionMonitor;
52  import org.apache.mina.util.NamePreservingRunnable;
53  import org.slf4j.Logger;
54  import org.slf4j.LoggerFactory;
55  
56  /**
57   * An abstract implementation of {@link IoProcessor} which helps transport
58   * developers to write an {@link IoProcessor} easily. This class is in charge of
59   * active polling a set of {@link IoSession} and trigger events when some I/O
60   * operation is possible.
61   * 
62   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
63   */
64  public abstract class AbstractPollingIoProcessor<T extends AbstractIoSession>
65          implements IoProcessor<T> {
66      /** A logger for this class */
67      private final static Logger LOG = LoggerFactory.getLogger(IoProcessor.class);
68  
69      /**
70       * The maximum loop count for a write operation until
71       * {@link #write(AbstractIoSession, IoBuffer, int)} returns non-zero value.
72       * It is similar to what a spin lock is for in concurrency programming. It
73       * improves memory utilization and write throughput significantly.
74       */
75      private static final int WRITE_SPIN_COUNT = 256;
76  
77      /**
78       * A timeout used for the select, as we need to get out to deal with idle
79       * sessions
80       */
81      private static final long SELECT_TIMEOUT = 1000L;
82  
83      /** A map containing the last Thread ID for each class */
84      private static final Map<Class<?>, AtomicInteger> threadIds = new ConcurrentHashMap<Class<?>, AtomicInteger>();
85  
86      /** A lock used to protect the processor creation */
87      private final Object lock = new Object();
88  
89      /** This IoProcessor instance name */
90      private final String threadName;
91  
92      /** The executor to use when we need to start the inner Processor */
93      private final Executor executor;
94  
95      /** A Session queue containing the newly created sessions */
96      private final Queue<T> newSessions = new ConcurrentLinkedQueue<T>();
97  
98      /** A queue used to store the sessions to be removed */
99      private final Queue<T> removingSessions = new ConcurrentLinkedQueue<T>();
100 
101     /** A queue used to store the sessions to be flushed */
102     private final Queue<T> flushingSessions = new ConcurrentLinkedQueue<T>();
103 
104     /**
105      * A queue used to store the sessions which have a trafficControl to be
106      * updated
107      */
108     private final Queue<T> trafficControllingSessions = new ConcurrentLinkedQueue<T>();
109 
110     /** The processor thread : it handles the incoming messages */
111     private Processor processor;
112 
113     private long lastIdleCheckTime;
114 
115     private final Object disposalLock = new Object();
116 
117     private volatile boolean disposing;
118 
119     private volatile boolean disposed;
120 
121     private final DefaultIoFuture disposalFuture = new DefaultIoFuture(null);
122 
123     protected AtomicBoolean wakeupCalled = new AtomicBoolean(false);
124 
125     /**
126      * Create an {@link AbstractPollingIoProcessor} with the given
127      * {@link Executor} for handling I/Os events.
128      * 
129      * @param executor
130      *            the {@link Executor} for handling I/O events
131      */
132     protected AbstractPollingIoProcessor(Executor executor) {
133         if (executor == null) {
134             throw new IllegalArgumentException("executor");
135         }
136 
137         this.threadName = nextThreadName();
138         this.executor = executor;
139     }
140 
141     /**
142      * Compute the thread ID for this class instance. As we may have different
143      * classes, we store the last ID number into a Map associating the class
144      * name to the last assigned ID.
145      * 
146      * @return a name for the current thread, based on the class name and an
147      *         incremental value, starting at 1.
148      */
149     private String nextThreadName() {
150         Class<?> cls = getClass();
151         int newThreadId;
152 
153         // We synchronize this block to avoid a concurrent access to
154         // the actomicInteger (it can be modified by another thread, while
155         // being seen as null by another thread)
156         synchronized (threadIds) {
157             // Get the current ID associated to this class' name
158             AtomicInteger threadId = threadIds.get(cls);
159 
160             if (threadId == null) {
161                 // We never have seen this class before, just create a
162                 // new ID starting at 1 for it, and associate this ID
163                 // with the class name in the map.
164                 newThreadId = 1;
165                 threadIds.put(cls, new AtomicInteger(newThreadId));
166             } else {
167                 // Just increment the lat ID, and get it.
168                 newThreadId = threadId.incrementAndGet();
169             }
170         }
171 
172         // Now we can compute the name for this thread
173         return cls.getSimpleName() + '-' + newThreadId;
174     }
175 
176     /**
177      * {@inheritDoc}
178      */
179     public final boolean isDisposing() {
180         return disposing;
181     }
182 
183     /**
184      * {@inheritDoc}
185      */
186     public final boolean isDisposed() {
187         return disposed;
188     }
189 
190     /**
191      * {@inheritDoc}
192      */
193     public final void dispose() {
194         if (disposed) {
195             return;
196         }
197 
198         synchronized (disposalLock) {
199             if (!disposing) {
200                 disposing = true;
201                 startupProcessor();
202             }
203         }
204 
205         disposalFuture.awaitUninterruptibly();
206         disposed = true;
207     }
208 
209     /**
210      * Dispose the resources used by this {@link IoProcessor} for polling the
211      * client connections
212      * 
213      * @throws Exception
214      *             if some low level IO error occurs
215      */
216     protected abstract void dispose0() throws Exception;
217 
218     /**
219      * poll those sessions for the given timeout
220      * 
221      * @param timeout
222      *            milliseconds before the call timeout if no event appear
223      * @return The number of session ready for read or for write
224      * @throws Exception
225      *             if some low level IO error occurs
226      */
227     protected abstract int select(long timeout) throws Exception;
228 
229     /**
230      * poll those sessions forever
231      * 
232      * @return The number of session ready for read or for write
233      * @throws Exception
234      *             if some low level IO error occurs
235      */
236     protected abstract int select() throws Exception;
237 
238     /**
239      * Say if the list of {@link IoSession} polled by this {@link IoProcessor}
240      * is empty
241      * 
242      * @return true if at least a session is managed by this {@link IoProcessor}
243      */
244     protected abstract boolean isSelectorEmpty();
245 
246     /**
247      * Interrupt the {@link AbstractPollingIoProcessor#select(int) call.
248      */
249     protected abstract void wakeup();
250 
251     /**
252      * Get an {@link Iterator} for the list of {@link IoSession} polled by this
253      * {@link IoProcessor}
254      * 
255      * @return {@link Iterator} of {@link IoSession}
256      */
257     protected abstract Iterator<T> allSessions();
258 
259     /**
260      * Get an {@link Iterator} for the list of {@link IoSession} found selected 
261      * by the last call of {@link AbstractPollingIoProcessor#select(int)
262      * @return {@link Iterator} of {@link IoSession} read for I/Os operation
263      */
264     protected abstract Iterator<T> selectedSessions();
265 
266     /**
267      * Get the state of a session (preparing, open, closed)
268      * 
269      * @param session
270      *            the {@link IoSession} to inspect
271      * @return the state of the session
272      */
273     protected abstract SessionState getState(T session);
274 
275     /**
276      * Is the session ready for writing
277      * 
278      * @param session
279      *            the session queried
280      * @return true is ready, false if not ready
281      */
282     protected abstract boolean isWritable(T session);
283 
284     /**
285      * Is the session ready for reading
286      * 
287      * @param session
288      *            the session queried
289      * @return true is ready, false if not ready
290      */
291     protected abstract boolean isReadable(T session);
292 
293     /**
294      * register a session for writing
295      * 
296      * @param session
297      *            the session registered
298      * @param isInterested
299      *            true for registering, false for removing
300      */
301     protected abstract void setInterestedInWrite(T session, boolean isInterested)
302             throws Exception;
303 
304     /**
305      * register a session for reading
306      * 
307      * @param session
308      *            the session registered
309      * @param isInterested
310      *            true for registering, false for removing
311      */
312     protected abstract void setInterestedInRead(T session, boolean isInterested)
313             throws Exception;
314 
315     /**
316      * is this session registered for reading
317      * 
318      * @param session
319      *            the session queried
320      * @return true is registered for reading
321      */
322     protected abstract boolean isInterestedInRead(T session);
323 
324     /**
325      * is this session registered for writing
326      * 
327      * @param session
328      *            the session queried
329      * @return true is registered for writing
330      */
331     protected abstract boolean isInterestedInWrite(T session);
332 
333     /**
334      * Initialize the polling of a session. Add it to the polling process.
335      * 
336      * @param session the {@link IoSession} to add to the polling
337      * @throws Exception any exception thrown by the underlying system calls
338      */
339     protected abstract void init(T session) throws Exception;
340 
341     /**
342      * Destroy the underlying client socket handle
343      * 
344      * @param session
345      *            the {@link IoSession}
346      * @throws Exception
347      *             any exception thrown by the underlying system calls
348      */
349     protected abstract void destroy(T session) throws Exception;
350 
351     /**
352      * Reads a sequence of bytes from a {@link IoSession} into the given
353      * {@link IoBuffer}. Is called when the session was found ready for reading.
354      * 
355      * @param session
356      *            the session to read
357      * @param buf
358      *            the buffer to fill
359      * @return the number of bytes read
360      * @throws Exception
361      *             any exception thrown by the underlying system calls
362      */
363     protected abstract int read(T session, IoBuffer buf) throws Exception;
364 
365     /**
366      * Write a sequence of bytes to a {@link IoSession}, means to be called when
367      * a session was found ready for writing.
368      * 
369      * @param session
370      *            the session to write
371      * @param buf
372      *            the buffer to write
373      * @param length
374      *            the number of bytes to write can be superior to the number of
375      *            bytes remaining in the buffer
376      * @return the number of byte written
377      * @throws Exception
378      *             any exception thrown by the underlying system calls
379      */
380     protected abstract int write(T session, IoBuffer buf, int length)
381             throws Exception;
382 
383     /**
384      * Write a part of a file to a {@link IoSession}, if the underlying API
385      * isn't supporting system calls like sendfile(), you can throw a
386      * {@link UnsupportedOperationException} so the file will be send using
387      * usual {@link #write(AbstractIoSession, IoBuffer, int)} call.
388      * 
389      * @param session
390      *            the session to write
391      * @param region
392      *            the file region to write
393      * @param length
394      *            the length of the portion to send
395      * @return the number of written bytes
396      * @throws Exception
397      *             any exception thrown by the underlying system calls
398      */
399     protected abstract int transferFile(T session, FileRegion region, int length)
400             throws Exception;
401 
402     /**
403      * {@inheritDoc}
404      */
405     public final void add(T session) {
406         if (isDisposing()) {
407             throw new IllegalStateException("Already disposed.");
408         }
409 
410         // Adds the session to the newSession queue and starts the worker
411         newSessions.add(session);
412         startupProcessor();
413     }
414 
415     /**
416      * {@inheritDoc}
417      */
418     public final void remove(T session) {
419         scheduleRemove(session);
420         startupProcessor();
421     }
422 
423     private void scheduleRemove(T session) {
424         removingSessions.add(session);
425     }
426 
427     /**
428      * {@inheritDoc}
429      */
430     public final void flush(T session) {
431         // add the session to the queue if it's not already
432         // in the queue, then wake up the select()
433         if (session.setScheduledForFlush( true )) {
434             flushingSessions.add(session);
435             wakeup();
436         }
437     }
438 
439     private void scheduleFlush(T session) {
440         // add the session to the queue if it's not already
441         // in the queue
442         if (session.setScheduledForFlush(true)) {
443             flushingSessions.add(session);
444         }
445     }
446 
447     /**
448      * {@inheritDoc}
449      */
450     public final void updateTrafficMask(T session) {
451         trafficControllingSessions.add(session);
452         wakeup();
453     }
454 
455     /**
456      * Starts the inner Processor, asking the executor to pick a thread in its
457      * pool. The Runnable will be renamed
458      */
459     private void startupProcessor() {
460         synchronized (lock) {
461             if (processor == null) {
462                 processor = new Processor();
463                 executor.execute(new NamePreservingRunnable(processor, threadName));
464             }
465         }
466 
467         // Just stop the select() and start it again, so that the processor
468         // can be activated immediately.
469         wakeup();
470     }
471 
472     /**
473      * In the case we are using the java select() method, this method is used to
474      * trash the buggy selector and create a new one, registring all the sockets
475      * on it.
476      * 
477      * @throws IOException
478      *             If we got an exception
479      */
480     abstract protected void registerNewSelector() throws IOException;
481 
482     /**
483      * Check that the select() has not exited immediately just because of a
484      * broken connection. In this case, this is a standard case, and we just
485      * have to loop.
486      * 
487      * @return true if a connection has been brutally closed.
488      * @throws IOException
489      *             If we got an exception
490      */
491     abstract protected boolean isBrokenConnection() throws IOException;
492 
493     /**
494      * Loops over the new sessions blocking queue and returns the number of
495      * sessions which are effectively created
496      * 
497      * @return The number of new sessions
498      */
499     private int handleNewSessions() {
500         int addedSessions = 0;
501 
502         for (T session = newSessions.poll(); session != null; session = newSessions.poll()) {
503             if (addNow(session)) {
504                 // A new session has been created
505                 addedSessions++;
506             }
507         }
508 
509         return addedSessions;
510     }
511 
512     /**
513      * Process a new session :
514      * - initialize it
515      * - create its chain
516      * - fire the CREATED listeners if any
517      *
518      * @param session The session to create
519      * @return true if the session has been registered
520      */
521     private boolean addNow(T session) {
522         boolean registered = false;
523 
524         try {
525             init(session);
526             registered = true;
527 
528             // Build the filter chain of this session.
529             IoFilterChainBuilder chainBuilder = session.getService().getFilterChainBuilder();
530             chainBuilder.buildFilterChain(session.getFilterChain());
531 
532             // DefaultIoFilterChain.CONNECT_FUTURE is cleared inside here
533             // in AbstractIoFilterChain.fireSessionOpened().
534             // Propagate the SESSION_CREATED event up to the chain
535             IoServiceListenerSupport listeners = ((AbstractIoService) session.getService()).getListeners();
536             listeners.fireSessionCreated(session);
537         } catch (Throwable e) {
538             ExceptionMonitor.getInstance().exceptionCaught(e);
539             
540             try {
541                 destroy(session);
542             } catch (Exception e1) {
543                 ExceptionMonitor.getInstance().exceptionCaught(e1);
544             } finally {
545                 registered = false;
546             }
547         }
548         
549         return registered;
550     }
551 
552     private int removeSessions() {
553         int removedSessions = 0;
554 
555         for (T session = removingSessions.poll();session != null;session = removingSessions.poll()) {
556             SessionState state = getState(session);
557 
558             // Now deal with the removal accordingly to the session's state
559             switch (state) {
560                 case OPENED:
561                     // Try to remove this session
562                     if (removeNow(session)) {
563                         removedSessions++;
564                     }
565     
566                     break;
567     
568                 case CLOSING:
569                     // Skip if channel is already closed
570                     break;
571     
572                 case OPENING:
573                     // Remove session from the newSessions queue and
574                     // remove it
575                     newSessions.remove(session);
576     
577                     if (removeNow(session)) {
578                         removedSessions++;
579                     }
580                     
581                     break;
582     
583                 default:
584                     throw new IllegalStateException(String.valueOf(state));
585             }
586         }
587         
588         return removedSessions;
589     }
590 
591     private boolean removeNow(T session) {
592         clearWriteRequestQueue(session);
593 
594         try {
595             destroy(session);
596             return true;
597         } catch (Exception e) {
598             IoFilterChain filterChain = session.getFilterChain();
599             filterChain.fireExceptionCaught(e);
600         } finally {
601             clearWriteRequestQueue(session);
602             ((AbstractIoService) session.getService()).getListeners()
603                     .fireSessionDestroyed(session);
604         }
605         return false;
606     }
607 
608     private void clearWriteRequestQueue(T session) {
609         WriteRequestQueue writeRequestQueue = session.getWriteRequestQueue();
610         WriteRequest req;
611 
612         List<WriteRequest> failedRequests = new ArrayList<WriteRequest>();
613 
614         if ((req = writeRequestQueue.poll(session)) != null) {
615             Object message = req.getMessage();
616             
617             if (message instanceof IoBuffer) {
618                 IoBuffer buf = (IoBuffer)message;
619 
620                 // The first unwritten empty buffer must be
621                 // forwarded to the filter chain.
622                 if (buf.hasRemaining()) {
623                     buf.reset();
624                     failedRequests.add(req);
625                 } else {
626                     IoFilterChain filterChain = session.getFilterChain();
627                     filterChain.fireMessageSent(req);
628                 }
629             } else {
630                 failedRequests.add(req);
631             }
632 
633             // Discard others.
634             while ((req = writeRequestQueue.poll(session)) != null) {
635                 failedRequests.add(req);
636             }
637         }
638 
639         // Create an exception and notify.
640         if (!failedRequests.isEmpty()) {
641             WriteToClosedSessionException cause = new WriteToClosedSessionException(
642                     failedRequests);
643             
644             for (WriteRequest r : failedRequests) {
645                 session.decreaseScheduledBytesAndMessages(r);
646                 r.getFuture().setException(cause);
647             }
648             
649             IoFilterChain filterChain = session.getFilterChain();
650             filterChain.fireExceptionCaught(cause);
651         }
652     }
653 
654     private void process() throws Exception {
655         for (Iterator<T> i = selectedSessions(); i.hasNext();) {
656             T session = i.next();
657             process(session);
658             i.remove();
659         }
660     }
661 
662     /**
663      * Deal with session ready for the read or write operations, or both.
664      */
665     private void process(T session) {
666         // Process Reads
667         if (isReadable(session) && !session.isReadSuspended()) {
668             read(session);
669         }
670 
671         // Process writes
672         if (isWritable(session) && !session.isWriteSuspended()) {
673             // add the session to the queue, if it's not already there
674             if (session.setScheduledForFlush(true)) {
675                 flushingSessions.add(session);
676             }       
677         }
678     }
679 
680     private void read(T session) {
681         IoSessionConfig config = session.getConfig();
682         int bufferSize = config.getReadBufferSize();
683         IoBuffer buf = IoBuffer.allocate(bufferSize);
684 
685         final boolean hasFragmentation = session.getTransportMetadata()
686                 .hasFragmentation();
687 
688         try {
689             int readBytes = 0;
690             int ret;
691 
692             try {
693                 if (hasFragmentation) {
694                     
695                     while ((ret = read(session, buf)) > 0) {
696                         readBytes += ret;
697                         
698                         if (!buf.hasRemaining()) {
699                             break;
700                         }
701                     }
702                 } else {
703                     ret = read(session, buf);
704                     
705                     if (ret > 0) {
706                         readBytes = ret;
707                     }
708                 }
709             } finally {
710                 buf.flip();
711             }
712 
713             if (readBytes > 0) {
714                 IoFilterChain filterChain = session.getFilterChain();
715                 filterChain.fireMessageReceived(buf);
716                 buf = null;
717 
718                 if (hasFragmentation) {
719                     if (readBytes << 1 < config.getReadBufferSize()) {
720                         session.decreaseReadBufferSize();
721                     } else if (readBytes == config.getReadBufferSize()) {
722                         session.increaseReadBufferSize();
723                     }
724                 }
725             }
726 
727             if (ret < 0) {
728                 scheduleRemove(session);
729             }
730         } catch (Throwable e) {
731             if (e instanceof IOException) {
732                 if (!(e instanceof PortUnreachableException)
733                         || !AbstractDatagramSessionConfig.class.isAssignableFrom(config.getClass())
734                         || ((AbstractDatagramSessionConfig) config).isCloseOnPortUnreachable()) {
735                     scheduleRemove(session);
736                 }
737             }
738 
739             IoFilterChain filterChain = session.getFilterChain();
740             filterChain.fireExceptionCaught(e);
741         }
742     }
743 
744     private void notifyIdleSessions(long currentTime) throws Exception {
745         // process idle sessions
746         if (currentTime - lastIdleCheckTime >= SELECT_TIMEOUT) {
747             lastIdleCheckTime = currentTime;
748             AbstractIoSession.notifyIdleness(allSessions(), currentTime);
749         }
750     }
751 
752     /**
753      * Write all the pending messages
754      */
755     private void flush(long currentTime) {
756         if (flushingSessions.isEmpty()) {
757             return;
758         }
759 
760         do {
761             T session = flushingSessions.poll(); // the same one with firstSession
762             
763             if (session == null) {
764                 // Just in case ... It should not happen.
765                 break;
766             }
767 
768             // Reset the Schedule for flush flag for this session,
769             // as we are flushing it now
770             session.unscheduledForFlush();
771             
772             SessionState state = getState(session);
773 
774             switch (state) {
775                 case OPENED:
776                     try {
777                         boolean flushedAll = flushNow(session, currentTime);
778                         
779                         if (flushedAll
780                                 && !session.getWriteRequestQueue().isEmpty(session)
781                                 && !session.isScheduledForFlush()) {
782                             scheduleFlush(session);
783                         }
784                     } catch (Exception e) {
785                         scheduleRemove(session);
786                         IoFilterChain filterChain = session.getFilterChain();
787                         filterChain.fireExceptionCaught(e);
788                     }
789     
790                     break;
791     
792                 case CLOSING:
793                     // Skip if the channel is already closed.
794                     break;
795     
796                 case OPENING:
797                     // Retry later if session is not yet fully initialized.
798                     // (In case that Session.write() is called before addSession()
799                     // is processed)
800                     scheduleFlush(session);
801                     return;
802     
803                 default:
804                     throw new IllegalStateException(String.valueOf(state));
805             }
806 
807         } while (!flushingSessions.isEmpty());
808     }
809 
810     private boolean flushNow(T session, long currentTime) {
811         if (!session.isConnected()) {
812             scheduleRemove(session);
813             return false;
814         }
815 
816         final boolean hasFragmentation = session.getTransportMetadata()
817                 .hasFragmentation();
818 
819         final WriteRequestQueue writeRequestQueue = session
820                 .getWriteRequestQueue();
821 
822         // Set limitation for the number of written bytes for read-write
823         // fairness. I used maxReadBufferSize * 3 / 2, which yields best
824         // performance in my experience while not breaking fairness much.
825         final int maxWrittenBytes = session.getConfig().getMaxReadBufferSize()
826                 + (session.getConfig().getMaxReadBufferSize() >>> 1);
827         int writtenBytes = 0;
828         WriteRequest req = null;
829         
830         try {
831             // Clear OP_WRITE
832             setInterestedInWrite(session, false);
833             
834             do {
835                 // Check for pending writes.
836                 req = session.getCurrentWriteRequest();
837                 
838                 if (req == null) {
839                     req = writeRequestQueue.poll(session);
840                     
841                     if (req == null) {
842                         break;
843                     }
844                     
845                     session.setCurrentWriteRequest(req);
846                 }
847 
848                 int localWrittenBytes = 0;
849                 Object message = req.getMessage();
850                 
851                 if (message instanceof IoBuffer) {
852                     localWrittenBytes = writeBuffer(session, req,
853                             hasFragmentation, maxWrittenBytes - writtenBytes,
854                             currentTime);
855                     
856                     if (localWrittenBytes > 0
857                             && ((IoBuffer) message).hasRemaining()) {
858                         // the buffer isn't empty, we re-interest it in writing
859                         writtenBytes += localWrittenBytes;
860                         setInterestedInWrite(session, true);
861                         return false;
862                     }
863                 } else if (message instanceof FileRegion) {
864                     localWrittenBytes = writeFile(session, req,
865                             hasFragmentation, maxWrittenBytes - writtenBytes,
866                             currentTime);
867 
868                     // Fix for Java bug on Linux
869                     // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5103988
870                     // If there's still data to be written in the FileRegion,
871                     // return 0 indicating that we need
872                     // to pause until writing may resume.
873                     if (localWrittenBytes > 0
874                             && ((FileRegion) message).getRemainingBytes() > 0) {
875                         writtenBytes += localWrittenBytes;
876                         setInterestedInWrite(session, true);
877                         return false;
878                     }
879                 } else {
880                     throw new IllegalStateException(
881                             "Don't know how to handle message of type '"
882                                     + message.getClass().getName()
883                                     + "'.  Are you missing a protocol encoder?");
884                 }
885 
886                 if (localWrittenBytes == 0) {
887                     // Kernel buffer is full.
888                     setInterestedInWrite(session, true);
889                     return false;
890                 }
891 
892                 writtenBytes += localWrittenBytes;
893 
894                 if (writtenBytes >= maxWrittenBytes) {
895                     // Wrote too much
896                     scheduleFlush(session);
897                     return false;
898                 }
899             } while (writtenBytes < maxWrittenBytes);
900         } catch (Exception e) {
901             if (req != null) {
902                 req.getFuture().setException(e);
903             }
904             
905             IoFilterChain filterChain = session.getFilterChain();
906             filterChain.fireExceptionCaught(e);
907             return false;
908         }
909 
910         return true;
911     }
912 
913     private int writeBuffer(T session, WriteRequest req,
914             boolean hasFragmentation, int maxLength, long currentTime)
915             throws Exception {
916         IoBuffer buf = (IoBuffer) req.getMessage();
917         int localWrittenBytes = 0;
918         
919         if (buf.hasRemaining()) {
920             int length;
921             
922             if (hasFragmentation) {
923                 length = Math.min(buf.remaining(), maxLength);
924             } else {
925                 length = buf.remaining();
926             }
927             
928             localWrittenBytes = write(session, buf, length);
929         }
930 
931         session.increaseWrittenBytes(localWrittenBytes, currentTime);
932 
933         if (!buf.hasRemaining() || !hasFragmentation && localWrittenBytes != 0) {
934             // Buffer has been sent, clear the current request.
935             int pos = buf.position();
936             buf.reset();
937             
938             fireMessageSent(session, req);
939             
940             // And set it back to its position
941             buf.position(pos);
942         }
943         return localWrittenBytes;
944     }
945 
946     private int writeFile(T session, WriteRequest req,
947             boolean hasFragmentation, int maxLength, long currentTime)
948             throws Exception {
949         int localWrittenBytes;
950         FileRegion region = (FileRegion) req.getMessage();
951         
952         if (region.getRemainingBytes() > 0) {
953             int length;
954             
955             if (hasFragmentation) {
956                 length = (int) Math.min(region.getRemainingBytes(), maxLength);
957             } else {
958                 length = (int) Math.min(Integer.MAX_VALUE, region
959                         .getRemainingBytes());
960             }
961             
962             localWrittenBytes = transferFile(session, region, length);
963             region.update(localWrittenBytes);
964         } else {
965             localWrittenBytes = 0;
966         }
967 
968         session.increaseWrittenBytes(localWrittenBytes, currentTime);
969 
970         if (region.getRemainingBytes() <= 0 || !hasFragmentation
971                 && localWrittenBytes != 0) {
972             fireMessageSent(session, req);
973         }
974 
975         return localWrittenBytes;
976     }
977 
978     private void fireMessageSent(T session, WriteRequest req) {
979         session.setCurrentWriteRequest(null);
980         IoFilterChain filterChain = session.getFilterChain();
981         filterChain.fireMessageSent(req);
982     }
983 
984     /**
985      * Update the trafficControl for all the session.
986      */
987     private void updateTrafficMask() {
988         int queueSize = trafficControllingSessions.size();
989 
990         while (queueSize > 0) {
991             T session = trafficControllingSessions.poll();
992 
993             if (session == null) {
994                 // We are done with this queue.
995                 return;
996             }
997 
998             SessionState state = getState(session);
999 
1000             switch (state) {
1001                 case OPENED:
1002                     updateTrafficControl(session);
1003 
1004                     break;
1005     
1006                 case CLOSING:
1007                     break;
1008     
1009                 case OPENING:
1010                     // Retry later if session is not yet fully initialized.
1011                     // (In case that Session.suspend??() or session.resume??() is
1012                     // called before addSession() is processed)
1013                     // We just put back the session at the end of the queue.
1014                     trafficControllingSessions.add(session);
1015                     break;
1016     
1017                 default:
1018                     throw new IllegalStateException(String.valueOf(state));
1019             }
1020             
1021             // As we have handled one session, decrement the number of
1022             // remaining sessions. The OPENING session will be processed
1023             // with the next select(), as the queue size has been decreased, even 
1024             // if the session has been pushed at the end of the queue
1025             queueSize--;
1026         }
1027     }
1028 
1029     /**
1030      * {@inheritDoc}
1031      */
1032     public void updateTrafficControl(T session) {
1033         // 
1034         try {
1035             setInterestedInRead(session, !session.isReadSuspended());
1036         } catch (Exception e) {
1037             IoFilterChain filterChain = session.getFilterChain();
1038             filterChain.fireExceptionCaught(e);
1039         }
1040 
1041         try {
1042             setInterestedInWrite(session, !session.getWriteRequestQueue()
1043                     .isEmpty(session)
1044                     && !session.isWriteSuspended());
1045         } catch (Exception e) {
1046             IoFilterChain filterChain = session.getFilterChain();
1047             filterChain.fireExceptionCaught(e);
1048         }
1049     }
1050 
1051     /**
1052      * The main loop. This is the place in charge to poll the Selector, and to 
1053      * process the active sessions. It's done in 
1054      * - handle the newly created sessions
1055      * - 
1056      */
1057     private class Processor implements Runnable {
1058         public void run() {
1059             int nSessions = 0;
1060             lastIdleCheckTime = System.currentTimeMillis();
1061 
1062             for (;;) {
1063                 try {
1064                     // This select has a timeout so that we can manage
1065                     // idle session when we get out of the select every
1066                     // second. (note : this is a hack to avoid creating
1067                     // a dedicated thread).
1068                     long t0 = System.currentTimeMillis();
1069                     int selected = select(SELECT_TIMEOUT);
1070                     long t1 = System.currentTimeMillis();
1071                     long delta = (t1 - t0);
1072 
1073                     if ((selected == 0) && !wakeupCalled.get() && (delta < 100)) {
1074                         // Last chance : the select() may have been
1075                         // interrupted because we have had an closed channel.
1076                         if (isBrokenConnection()) {
1077                             LOG.warn("Broken connection");
1078 
1079                             // we can reselect immediately
1080                             // set back the flag to false
1081                             wakeupCalled.getAndSet(false);
1082 
1083                             continue;
1084                         } else {
1085                             LOG.warn("Create a new selector. Selected is 0, delta = "
1086                                             + (t1 - t0));
1087                             // Ok, we are hit by the nasty epoll
1088                             // spinning.
1089                             // Basically, there is a race condition
1090                             // which causes a closing file descriptor not to be
1091                             // considered as available as a selected channel, but
1092                             // it stopped the select. The next time we will
1093                             // call select(), it will exit immediately for the same
1094                             // reason, and do so forever, consuming 100%
1095                             // CPU.
1096                             // We have to destroy the selector, and
1097                             // register all the socket on a new one.
1098                             registerNewSelector();
1099                         }
1100 
1101                         // Set back the flag to false
1102                         wakeupCalled.getAndSet(false);
1103                         
1104                         // and continue the loop
1105                         continue;
1106                     }
1107 
1108                     // Manage newly created session first
1109                     nSessions += handleNewSessions();
1110                     
1111                     updateTrafficMask();
1112 
1113                     // Now, if we have had some incoming or outgoing events,
1114                     // deal with them
1115                     if (selected > 0) {
1116                         //LOG.debug("Processing ..."); // This log hurts one of the MDCFilter test...
1117                         process();
1118                     }
1119 
1120                     // Write the pending requests
1121                     long currentTime = System.currentTimeMillis();
1122                     flush(currentTime);
1123                     
1124                     // And manage removed sessions
1125                     nSessions -= removeSessions();
1126                     
1127                     // Last, not least, send Idle events to the idle sessions
1128                     notifyIdleSessions(currentTime);
1129 
1130                     // Get a chance to exit the infinite loop if there are no
1131                     // more sessions on this Processor
1132                     if (nSessions == 0) {
1133                         synchronized (lock) {
1134                             if (newSessions.isEmpty() && isSelectorEmpty()) {
1135                                 processor = null;
1136                                 break;
1137                             }
1138                         }
1139                     }
1140 
1141                     // Disconnect all sessions immediately if disposal has been
1142                     // requested so that we exit this loop eventually.
1143                     if (isDisposing()) {
1144                         for (Iterator<T> i = allSessions(); i.hasNext();) {
1145                             scheduleRemove(i.next());
1146                         }
1147                         
1148                         wakeup();
1149                     }
1150                 } catch (Throwable t) {
1151                     ExceptionMonitor.getInstance().exceptionCaught(t);
1152 
1153                     try {
1154                         Thread.sleep(1000);
1155                     } catch (InterruptedException e1) {
1156                         ExceptionMonitor.getInstance().exceptionCaught(e1);
1157                     }
1158                 }
1159             }
1160 
1161             try {
1162                 synchronized (disposalLock) {
1163                     if (isDisposing()) {
1164                         dispose0();
1165                     }
1166                 }
1167             } catch (Throwable t) {
1168                 ExceptionMonitor.getInstance().exceptionCaught(t);
1169             } finally {
1170                 disposalFuture.setValue(true);
1171             }
1172         }
1173     }
1174 }