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.nio.channels.ClosedSelectorException;
25  import java.util.ArrayList;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Queue;
30  import java.util.concurrent.ConcurrentHashMap;
31  import java.util.concurrent.ConcurrentLinkedQueue;
32  import java.util.concurrent.Executor;
33  import java.util.concurrent.atomic.AtomicBoolean;
34  import java.util.concurrent.atomic.AtomicInteger;
35  import java.util.concurrent.atomic.AtomicReference;
36  
37  import org.apache.mina.core.buffer.IoBuffer;
38  import org.apache.mina.core.file.FileRegion;
39  import org.apache.mina.core.filterchain.IoFilterChain;
40  import org.apache.mina.core.filterchain.IoFilterChainBuilder;
41  import org.apache.mina.core.future.DefaultIoFuture;
42  import org.apache.mina.core.service.AbstractIoService;
43  import org.apache.mina.core.service.IoProcessor;
44  import org.apache.mina.core.service.IoServiceListenerSupport;
45  import org.apache.mina.core.session.AbstractIoSession;
46  import org.apache.mina.core.session.IoSession;
47  import org.apache.mina.core.session.IoSessionConfig;
48  import org.apache.mina.core.session.SessionState;
49  import org.apache.mina.core.write.WriteRequest;
50  import org.apache.mina.core.write.WriteRequestQueue;
51  import org.apache.mina.core.write.WriteToClosedSessionException;
52  import org.apache.mina.transport.socket.AbstractDatagramSessionConfig;
53  import org.apache.mina.util.ExceptionMonitor;
54  import org.apache.mina.util.NamePreservingRunnable;
55  import org.slf4j.Logger;
56  import org.slf4j.LoggerFactory;
57  
58  /**
59   * An abstract implementation of {@link IoProcessor} which helps transport
60   * developers to write an {@link IoProcessor} easily. This class is in charge of
61   * active polling a set of {@link IoSession} and trigger events when some I/O
62   * operation is possible.
63   *
64   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
65   *
66   * @param <S> the type of the {@link IoSession} this processor can handle
67   */
68  public abstract class AbstractPollingIoProcessor<S extends AbstractIoSession> implements IoProcessor<S> {
69      /** A logger for this class */
70      private final static Logger LOG = LoggerFactory.getLogger(IoProcessor.class);
71  
72      /**
73       * The maximum loop count for a write operation until
74       * {@link #write(AbstractIoSession, IoBuffer, int)} returns non-zero value.
75       * It is similar to what a spin lock is for in concurrency programming. It
76       * improves memory utilization and write throughput significantly.
77       */
78      private static final int WRITE_SPIN_COUNT = 256;
79  
80      /**
81       * A timeout used for the select, as we need to get out to deal with idle
82       * sessions
83       */
84      private static final long SELECT_TIMEOUT = 1000L;
85  
86      /** A map containing the last Thread ID for each class */
87      private static final Map<Class<?>, AtomicInteger> threadIds = new ConcurrentHashMap<Class<?>, AtomicInteger>();
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<S> newSessions = new ConcurrentLinkedQueue<S>();
97  
98      /** A queue used to store the sessions to be removed */
99      private final Queue<S> removingSessions = new ConcurrentLinkedQueue<S>();
100 
101     /** A queue used to store the sessions to be flushed */
102     private final Queue<S> flushingSessions = new ConcurrentLinkedQueue<S>();
103 
104     /**
105      * A queue used to store the sessions which have a trafficControl to be
106      * updated
107      */
108     private final Queue<S> trafficControllingSessions = new ConcurrentLinkedQueue<S>();
109 
110     /** The processor thread : it handles the incoming messages */
111     private final AtomicReference<Processor> processorRef = new AtomicReference<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 || disposing) {
195             return;
196         }
197 
198         synchronized (disposalLock) {
199             disposing = true;
200             startupProcessor();
201         }
202 
203         disposalFuture.awaitUninterruptibly();
204         disposed = true;
205     }
206 
207     /**
208      * Dispose the resources used by this {@link IoProcessor} for polling the
209      * client connections. The implementing class doDispose method will be called.
210      *
211      * @throws Exception if some low level IO error occurs
212      */
213     protected abstract void doDispose() throws Exception;
214 
215     /**
216      * poll those sessions for the given timeout
217      *
218      * @param timeout
219      *            milliseconds before the call timeout if no event appear
220      * @return The number of session ready for read or for write
221      * @throws Exception
222      *             if some low level IO error occurs
223      */
224     protected abstract int select(long timeout) throws Exception;
225 
226     /**
227      * poll those sessions forever
228      *
229      * @return The number of session ready for read or for write
230      * @throws Exception
231      *             if some low level IO error occurs
232      */
233     protected abstract int select() throws Exception;
234 
235     /**
236      * Say if the list of {@link IoSession} polled by this {@link IoProcessor}
237      * is empty
238      *
239      * @return true if at least a session is managed by this {@link IoProcessor}
240      */
241     protected abstract boolean isSelectorEmpty();
242 
243     /**
244      * Interrupt the {@link AbstractPollingIoProcessor#select(int) call.
245      */
246     protected abstract void wakeup();
247 
248     /**
249      * Get an {@link Iterator} for the list of {@link IoSession} polled by this
250      * {@link IoProcessor}
251      *
252      * @return {@link Iterator} of {@link IoSession}
253      */
254     protected abstract Iterator<S> allSessions();
255 
256     /**
257      * Get an {@link Iterator} for the list of {@link IoSession} found selected
258      * by the last call of {@link AbstractPollingIoProcessor#select(int)
259      * @return {@link Iterator} of {@link IoSession} read for I/Os operation
260      */
261     protected abstract Iterator<S> selectedSessions();
262 
263     /**
264      * Get the state of a session (preparing, open, closed)
265      *
266      * @param session
267      *            the {@link IoSession} to inspect
268      * @return the state of the session
269      */
270     protected abstract SessionState getState(S session);
271 
272     /**
273      * Is the session ready for writing
274      *
275      * @param session
276      *            the session queried
277      * @return true is ready, false if not ready
278      */
279     protected abstract boolean isWritable(S session);
280 
281     /**
282      * Is the session ready for reading
283      *
284      * @param session
285      *            the session queried
286      * @return true is ready, false if not ready
287      */
288     protected abstract boolean isReadable(S session);
289 
290     /**
291      * register a session for writing
292      *
293      * @param session
294      *            the session registered
295      * @param isInterested
296      *            true for registering, false for removing
297      */
298     protected abstract void setInterestedInWrite(S session, boolean isInterested)
299             throws Exception;
300 
301     /**
302      * register a session for reading
303      *
304      * @param session
305      *            the session registered
306      * @param isInterested
307      *            true for registering, false for removing
308      */
309     protected abstract void setInterestedInRead(S session, boolean isInterested)
310             throws Exception;
311 
312     /**
313      * is this session registered for reading
314      *
315      * @param session
316      *            the session queried
317      * @return true is registered for reading
318      */
319     protected abstract boolean isInterestedInRead(S session);
320 
321     /**
322      * is this session registered for writing
323      *
324      * @param session
325      *            the session queried
326      * @return true is registered for writing
327      */
328     protected abstract boolean isInterestedInWrite(S session);
329 
330     /**
331      * Initialize the polling of a session. Add it to the polling process.
332      *
333      * @param session the {@link IoSession} to add to the polling
334      * @throws Exception any exception thrown by the underlying system calls
335      */
336     protected abstract void init(S session) throws Exception;
337 
338     /**
339      * Destroy the underlying client socket handle
340      *
341      * @param session
342      *            the {@link IoSession}
343      * @throws Exception
344      *             any exception thrown by the underlying system calls
345      */
346     protected abstract void destroy(S session) throws Exception;
347 
348     /**
349      * Reads a sequence of bytes from a {@link IoSession} into the given
350      * {@link IoBuffer}. Is called when the session was found ready for reading.
351      *
352      * @param session
353      *            the session to read
354      * @param buf
355      *            the buffer to fill
356      * @return the number of bytes read
357      * @throws Exception
358      *             any exception thrown by the underlying system calls
359      */
360     protected abstract int read(S session, IoBuffer buf) throws Exception;
361 
362     /**
363      * Write a sequence of bytes to a {@link IoSession}, means to be called when
364      * a session was found ready for writing.
365      *
366      * @param session
367      *            the session to write
368      * @param buf
369      *            the buffer to write
370      * @param length
371      *            the number of bytes to write can be superior to the number of
372      *            bytes remaining in the buffer
373      * @return the number of byte written
374      * @throws Exception
375      *             any exception thrown by the underlying system calls
376      */
377     protected abstract int write(S session, IoBuffer buf, int length)
378             throws Exception;
379 
380     /**
381      * Write a part of a file to a {@link IoSession}, if the underlying API
382      * isn't supporting system calls like sendfile(), you can throw a
383      * {@link UnsupportedOperationException} so the file will be send using
384      * usual {@link #write(AbstractIoSession, IoBuffer, int)} call.
385      *
386      * @param session
387      *            the session to write
388      * @param region
389      *            the file region to write
390      * @param length
391      *            the length of the portion to send
392      * @return the number of written bytes
393      * @throws Exception
394      *             any exception thrown by the underlying system calls
395      */
396     protected abstract int transferFile(S session, FileRegion region, int length)
397             throws Exception;
398 
399     /**
400      * {@inheritDoc}
401      */
402     public final void add(S session) {
403         if (disposed || disposing) {
404             throw new IllegalStateException("Already disposed.");
405         }
406 
407         // Adds the session to the newSession queue and starts the worker
408         newSessions.add(session);
409         startupProcessor();
410     }
411 
412     /**
413      * {@inheritDoc}
414      */
415     public final void remove(S session) {
416         scheduleRemove(session);
417         startupProcessor();
418     }
419 
420     private void scheduleRemove(S session) {
421         removingSessions.add(session);
422     }
423 
424     /**
425      * {@inheritDoc}
426      */
427     public final void flush(S session) {
428         // add the session to the queue if it's not already
429         // in the queue, then wake up the select()
430         if (session.setScheduledForFlush( true )) {
431             flushingSessions.add(session);
432             wakeup();
433         }
434     }
435 
436     private void scheduleFlush(S session) {
437         // add the session to the queue if it's not already
438         // in the queue
439         if (session.setScheduledForFlush(true)) {
440             flushingSessions.add(session);
441         }
442     }
443 
444     /**
445      * {@inheritDoc}
446      */
447     public final void updateTrafficMask(S session) {
448         trafficControllingSessions.add(session);
449         wakeup();
450     }
451 
452     /**
453      * Starts the inner Processor, asking the executor to pick a thread in its
454      * pool. The Runnable will be renamed
455      */
456     private void startupProcessor() {
457         Processor processor = processorRef.get();
458 
459         if (processor == null) {
460             processor = new Processor();
461 
462             if (processorRef.compareAndSet(null, 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 (S 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(S 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 (S 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(S 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(S 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<S> i = selectedSessions(); i.hasNext();) {
656             S 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(S 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(S 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 
745     private static String byteArrayToHex( byte[] barray )
746     {
747         char[] c = new char[barray.length * 2];
748         int pos = 0;
749 
750         for ( byte b : barray )
751         {
752             int bb = ( b & 0x00FF ) >> 4;
753             c[pos++] = ( char ) ( bb > 9 ? bb + 0x37 : bb + 0x30 );
754             bb = b & 0x0F;
755             c[pos++] = ( char ) ( bb > 9 ? bb + 0x37 : bb + 0x30 );
756             if ( pos > 60 )
757             {
758                 break;
759             }
760         }
761 
762         return new String( c );
763     }
764 
765 
766     private void notifyIdleSessions(long currentTime) throws Exception {
767         // process idle sessions
768         if (currentTime - lastIdleCheckTime >= SELECT_TIMEOUT) {
769             lastIdleCheckTime = currentTime;
770             AbstractIoSession.notifyIdleness(allSessions(), currentTime);
771         }
772     }
773 
774     /**
775      * Write all the pending messages
776      */
777     private void flush(long currentTime) {
778         if (flushingSessions.isEmpty()) {
779             return;
780         }
781 
782         do {
783             S session = flushingSessions.poll(); // the same one with firstSession
784 
785             if (session == null) {
786                 // Just in case ... It should not happen.
787                 break;
788             }
789 
790             // Reset the Schedule for flush flag for this session,
791             // as we are flushing it now
792             session.unscheduledForFlush();
793 
794             SessionState state = getState(session);
795 
796             switch (state) {
797                 case OPENED:
798                     try {
799                         boolean flushedAll = flushNow(session, currentTime);
800 
801                         if (flushedAll
802                                 && !session.getWriteRequestQueue().isEmpty(session)
803                                 && !session.isScheduledForFlush()) {
804                             scheduleFlush(session);
805                         }
806                     } catch (Exception e) {
807                         scheduleRemove(session);
808                         IoFilterChain filterChain = session.getFilterChain();
809                         filterChain.fireExceptionCaught(e);
810                     }
811 
812                     break;
813 
814                 case CLOSING:
815                     // Skip if the channel is already closed.
816                     break;
817 
818                 case OPENING:
819                     // Retry later if session is not yet fully initialized.
820                     // (In case that Session.write() is called before addSession()
821                     // is processed)
822                     scheduleFlush(session);
823                     return;
824 
825                 default:
826                     throw new IllegalStateException(String.valueOf(state));
827             }
828 
829         } while (!flushingSessions.isEmpty());
830     }
831 
832     private boolean flushNow(S session, long currentTime) {
833         if (!session.isConnected()) {
834             scheduleRemove(session);
835             return false;
836         }
837 
838         final boolean hasFragmentation = session.getTransportMetadata()
839                 .hasFragmentation();
840 
841         final WriteRequestQueue writeRequestQueue = session
842                 .getWriteRequestQueue();
843 
844         // Set limitation for the number of written bytes for read-write
845         // fairness. I used maxReadBufferSize * 3 / 2, which yields best
846         // performance in my experience while not breaking fairness much.
847         final int maxWrittenBytes = session.getConfig().getMaxReadBufferSize()
848                 + (session.getConfig().getMaxReadBufferSize() >>> 1);
849         int writtenBytes = 0;
850         WriteRequest req = null;
851 
852         try {
853             // Clear OP_WRITE
854             setInterestedInWrite(session, false);
855 
856             do {
857                 // Check for pending writes.
858                 req = session.getCurrentWriteRequest();
859 
860                 if (req == null) {
861                     req = writeRequestQueue.poll(session);
862 
863                     if (req == null) {
864                         break;
865                     }
866 
867                     session.setCurrentWriteRequest(req);
868                 }
869 
870                 int localWrittenBytes = 0;
871                 Object message = req.getMessage();
872 
873                 if (message instanceof IoBuffer) {
874                     localWrittenBytes = writeBuffer(session, req,
875                             hasFragmentation, maxWrittenBytes - writtenBytes,
876                             currentTime);
877 
878                     if (( localWrittenBytes > 0 )
879                             && ((IoBuffer) message).hasRemaining()) {
880                         // the buffer isn't empty, we re-interest it in writing
881                         writtenBytes += localWrittenBytes;
882                         setInterestedInWrite(session, true);
883                         return false;
884                     }
885                 } else if (message instanceof FileRegion) {
886                     localWrittenBytes = writeFile(session, req,
887                             hasFragmentation, maxWrittenBytes - writtenBytes,
888                             currentTime);
889 
890                     // Fix for Java bug on Linux
891                     // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5103988
892                     // If there's still data to be written in the FileRegion,
893                     // return 0 indicating that we need
894                     // to pause until writing may resume.
895                     if (( localWrittenBytes > 0 )
896                             && ( ((FileRegion) message).getRemainingBytes() > 0 )) {
897                         writtenBytes += localWrittenBytes;
898                         setInterestedInWrite(session, true);
899                         return false;
900                     }
901                 } else {
902                     throw new IllegalStateException(
903                             "Don't know how to handle message of type '"
904                                     + message.getClass().getName()
905                                     + "'.  Are you missing a protocol encoder?");
906                 }
907 
908                 if (localWrittenBytes == 0) {
909                     // Kernel buffer is full.
910                     setInterestedInWrite(session, true);
911                     return false;
912                 }
913 
914                 writtenBytes += localWrittenBytes;
915 
916                 if (writtenBytes >= maxWrittenBytes) {
917                     // Wrote too much
918                     scheduleFlush(session);
919                     return false;
920                 }
921             } while (writtenBytes < maxWrittenBytes);
922         } catch (Exception e) {
923             if (req != null) {
924                 req.getFuture().setException(e);
925             }
926 
927             IoFilterChain filterChain = session.getFilterChain();
928             filterChain.fireExceptionCaught(e);
929             return false;
930         }
931 
932         return true;
933     }
934 
935     private int writeBuffer(S session, WriteRequest req,
936             boolean hasFragmentation, int maxLength, long currentTime)
937             throws Exception {
938         IoBuffer buf = (IoBuffer) req.getMessage();
939         int localWrittenBytes = 0;
940 
941         if (buf.hasRemaining()) {
942             int length;
943 
944             if (hasFragmentation) {
945                 length = Math.min(buf.remaining(), maxLength);
946             } else {
947                 length = buf.remaining();
948             }
949 
950             localWrittenBytes = write(session, buf, length);
951         }
952 
953         session.increaseWrittenBytes(localWrittenBytes, currentTime);
954 
955         if (!buf.hasRemaining() || ( !hasFragmentation && ( localWrittenBytes != 0 ) )) {
956             // Buffer has been sent, clear the current request.
957             int pos = buf.position();
958             buf.reset();
959 
960             fireMessageSent(session, req);
961 
962             // And set it back to its position
963             buf.position(pos);
964         }
965         return localWrittenBytes;
966     }
967 
968     private int writeFile(S session, WriteRequest req,
969             boolean hasFragmentation, int maxLength, long currentTime)
970             throws Exception {
971         int localWrittenBytes;
972         FileRegion region = (FileRegion) req.getMessage();
973 
974         if (region.getRemainingBytes() > 0) {
975             int length;
976 
977             if (hasFragmentation) {
978                 length = (int) Math.min(region.getRemainingBytes(), maxLength);
979             } else {
980                 length = (int) Math.min(Integer.MAX_VALUE, region
981                         .getRemainingBytes());
982             }
983 
984             localWrittenBytes = transferFile(session, region, length);
985             region.update(localWrittenBytes);
986         } else {
987             localWrittenBytes = 0;
988         }
989 
990         session.increaseWrittenBytes(localWrittenBytes, currentTime);
991 
992         if (( region.getRemainingBytes() <= 0 ) || ( !hasFragmentation
993                 && ( localWrittenBytes != 0 ) )) {
994             fireMessageSent(session, req);
995         }
996 
997         return localWrittenBytes;
998     }
999 
1000     private void fireMessageSent(S session, WriteRequest req) {
1001         session.setCurrentWriteRequest(null);
1002         IoFilterChain filterChain = session.getFilterChain();
1003         filterChain.fireMessageSent(req);
1004     }
1005 
1006     /**
1007      * Update the trafficControl for all the session.
1008      */
1009     private void updateTrafficMask() {
1010         int queueSize = trafficControllingSessions.size();
1011 
1012         while (queueSize > 0) {
1013             S session = trafficControllingSessions.poll();
1014 
1015             if (session == null) {
1016                 // We are done with this queue.
1017                 return;
1018             }
1019 
1020             SessionState state = getState(session);
1021 
1022             switch (state) {
1023                 case OPENED:
1024                     updateTrafficControl(session);
1025 
1026                     break;
1027 
1028                 case CLOSING:
1029                     break;
1030 
1031                 case OPENING:
1032                     // Retry later if session is not yet fully initialized.
1033                     // (In case that Session.suspend??() or session.resume??() is
1034                     // called before addSession() is processed)
1035                     // We just put back the session at the end of the queue.
1036                     trafficControllingSessions.add(session);
1037                     break;
1038 
1039                 default:
1040                     throw new IllegalStateException(String.valueOf(state));
1041             }
1042 
1043             // As we have handled one session, decrement the number of
1044             // remaining sessions. The OPENING session will be processed
1045             // with the next select(), as the queue size has been decreased, even
1046             // if the session has been pushed at the end of the queue
1047             queueSize--;
1048         }
1049     }
1050 
1051     /**
1052      * {@inheritDoc}
1053      */
1054     public void updateTrafficControl(S session) {
1055         //
1056         try {
1057             setInterestedInRead(session, !session.isReadSuspended());
1058         } catch (Exception e) {
1059             IoFilterChain filterChain = session.getFilterChain();
1060             filterChain.fireExceptionCaught(e);
1061         }
1062 
1063         try {
1064             setInterestedInWrite(session, !session.getWriteRequestQueue()
1065                     .isEmpty(session)
1066                     && !session.isWriteSuspended());
1067         } catch (Exception e) {
1068             IoFilterChain filterChain = session.getFilterChain();
1069             filterChain.fireExceptionCaught(e);
1070         }
1071     }
1072 
1073     /**
1074      * The main loop. This is the place in charge to poll the Selector, and to
1075      * process the active sessions. It's done in
1076      * - handle the newly created sessions
1077      * -
1078      */
1079     private class Processor implements Runnable {
1080         public void run() {
1081             assert (processorRef.get() == this);
1082 
1083             int nSessions = 0;
1084             lastIdleCheckTime = System.currentTimeMillis();
1085 
1086             for (;;) {
1087                 try {
1088                     // This select has a timeout so that we can manage
1089                     // idle session when we get out of the select every
1090                     // second. (note : this is a hack to avoid creating
1091                     // a dedicated thread).
1092                     long t0 = System.currentTimeMillis();
1093                     int selected = select(SELECT_TIMEOUT);
1094                     long t1 = System.currentTimeMillis();
1095                     long delta = (t1 - t0);
1096 
1097                     if ((selected == 0) && !wakeupCalled.get() && (delta < 100)) {
1098                         // Last chance : the select() may have been
1099                         // interrupted because we have had an closed channel.
1100                         if (isBrokenConnection()) {
1101                             LOG.warn("Broken connection");
1102 
1103                             // we can reselect immediately
1104                             // set back the flag to false
1105                             wakeupCalled.getAndSet(false);
1106 
1107                             continue;
1108                         } else {
1109                             LOG.warn("Create a new selector. Selected is 0, delta = "
1110                                             + (t1 - t0));
1111                             // Ok, we are hit by the nasty epoll
1112                             // spinning.
1113                             // Basically, there is a race condition
1114                             // which causes a closing file descriptor not to be
1115                             // considered as available as a selected channel, but
1116                             // it stopped the select. The next time we will
1117                             // call select(), it will exit immediately for the same
1118                             // reason, and do so forever, consuming 100%
1119                             // CPU.
1120                             // We have to destroy the selector, and
1121                             // register all the socket on a new one.
1122                             registerNewSelector();
1123                         }
1124 
1125                         // Set back the flag to false
1126                         wakeupCalled.getAndSet(false);
1127 
1128                         // and continue the loop
1129                         continue;
1130                     }
1131 
1132                     // Manage newly created session first
1133                     nSessions += handleNewSessions();
1134 
1135                     updateTrafficMask();
1136 
1137                     // Now, if we have had some incoming or outgoing events,
1138                     // deal with them
1139                     if (selected > 0) {
1140                         //LOG.debug("Processing ..."); // This log hurts one of the MDCFilter test...
1141                         process();
1142                     }
1143 
1144                     // Write the pending requests
1145                     long currentTime = System.currentTimeMillis();
1146                     flush(currentTime);
1147 
1148                     // And manage removed sessions
1149                     nSessions -= removeSessions();
1150 
1151                     // Last, not least, send Idle events to the idle sessions
1152                     notifyIdleSessions(currentTime);
1153 
1154                     // Get a chance to exit the infinite loop if there are no
1155                     // more sessions on this Processor
1156                     if (nSessions == 0) {
1157                         processorRef.set(null);
1158                         
1159                         if (newSessions.isEmpty() && isSelectorEmpty()) {
1160                             // newSessions.add() precedes startupProcessor
1161                             assert (processorRef.get() != this);
1162                             break;
1163                         }
1164                         
1165                         assert (processorRef.get() != this);
1166                         
1167                         if (!processorRef.compareAndSet(null, this)) {
1168                             // startupProcessor won race, so must exit processor
1169                             assert (processorRef.get() != this);
1170                             break;
1171                         }
1172                         
1173                         assert (processorRef.get() == this);
1174                     }
1175 
1176                     // Disconnect all sessions immediately if disposal has been
1177                     // requested so that we exit this loop eventually.
1178                     if (isDisposing()) {
1179                         for (Iterator<S> i = allSessions(); i.hasNext();) {
1180                             scheduleRemove(i.next());
1181                         }
1182 
1183                         wakeup();
1184                     }
1185                 } catch (ClosedSelectorException cse) {
1186                     // If the selector has been closed, we can exit the loop
1187                     break;
1188                 } catch (Throwable t) {
1189                     ExceptionMonitor.getInstance().exceptionCaught(t);
1190 
1191                     try {
1192                         Thread.sleep(1000);
1193                     } catch (InterruptedException e1) {
1194                         ExceptionMonitor.getInstance().exceptionCaught(e1);
1195                     }
1196                 }
1197             }
1198 
1199             try {
1200                 synchronized (disposalLock) {
1201                     if (disposing) {
1202                         doDispose();
1203                     }
1204                 }
1205             } catch (Throwable t) {
1206                 ExceptionMonitor.getInstance().exceptionCaught(t);
1207             } finally {
1208                 disposalFuture.setValue(true);
1209             }
1210         }
1211     }
1212 }