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.session;
21  
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.IOException;
25  import java.net.SocketAddress;
26  import java.nio.channels.FileChannel;
27  import java.util.Iterator;
28  import java.util.Queue;
29  import java.util.Set;
30  import java.util.concurrent.ConcurrentLinkedQueue;
31  import java.util.concurrent.atomic.AtomicBoolean;
32  import java.util.concurrent.atomic.AtomicInteger;
33  import java.util.concurrent.atomic.AtomicLong;
34  
35  import org.apache.mina.core.buffer.IoBuffer;
36  import org.apache.mina.core.file.DefaultFileRegion;
37  import org.apache.mina.core.file.FilenameFileRegion;
38  import org.apache.mina.core.filterchain.IoFilterChain;
39  import org.apache.mina.core.future.CloseFuture;
40  import org.apache.mina.core.future.DefaultCloseFuture;
41  import org.apache.mina.core.future.DefaultReadFuture;
42  import org.apache.mina.core.future.DefaultWriteFuture;
43  import org.apache.mina.core.future.IoFutureListener;
44  import org.apache.mina.core.future.ReadFuture;
45  import org.apache.mina.core.future.WriteFuture;
46  import org.apache.mina.core.service.AbstractIoService;
47  import org.apache.mina.core.service.IoAcceptor;
48  import org.apache.mina.core.service.IoHandler;
49  import org.apache.mina.core.service.IoProcessor;
50  import org.apache.mina.core.service.IoService;
51  import org.apache.mina.core.service.TransportMetadata;
52  import org.apache.mina.core.write.DefaultWriteRequest;
53  import org.apache.mina.core.write.WriteException;
54  import org.apache.mina.core.write.WriteRequest;
55  import org.apache.mina.core.write.WriteRequestQueue;
56  import org.apache.mina.core.write.WriteTimeoutException;
57  import org.apache.mina.core.write.WriteToClosedSessionException;
58  import org.apache.mina.util.ExceptionMonitor;
59  
60  /**
61   * Base implementation of {@link IoSession}.
62   *
63   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
64   */
65  public abstract class AbstractIoSession implements IoSession {
66      /** The associated handler */
67      private final IoHandler handler;
68  
69      /** The session config */
70      protected IoSessionConfig config;
71  
72      /** The service which will manage this session */
73      private final IoService service;
74  
75      private static final AttributeKey READY_READ_FUTURES_KEY = new AttributeKey(AbstractIoSession.class,
76              "readyReadFutures");
77  
78      private static final AttributeKey WAITING_READ_FUTURES_KEY = new AttributeKey(AbstractIoSession.class,
79              "waitingReadFutures");
80  
81      private static final IoFutureListener<CloseFuture> SCHEDULED_COUNTER_RESETTER = new IoFutureListener<CloseFuture>() {
82          public void operationComplete(CloseFuture future) {
83              AbstractIoSession session = (AbstractIoSession) future.getSession();
84              session.scheduledWriteBytes.set(0);
85              session.scheduledWriteMessages.set(0);
86              session.readBytesThroughput = 0;
87              session.readMessagesThroughput = 0;
88              session.writtenBytesThroughput = 0;
89              session.writtenMessagesThroughput = 0;
90          }
91      };
92  
93      /**
94       * An internal write request object that triggers session close.
95       *
96       * @see #writeRequestQueue
97       */
98      private static final WriteRequest CLOSE_REQUEST = new DefaultWriteRequest(new Object());
99  
100     private final Object lock = new Object();
101 
102     private IoSessionAttributeMap attributes;
103 
104     private WriteRequestQueue writeRequestQueue;
105 
106     private WriteRequest currentWriteRequest;
107 
108     /** The Session creation's time */
109     private final long creationTime;
110 
111     /** An id generator guaranteed to generate unique IDs for the session */
112     private static AtomicLong idGenerator = new AtomicLong(0);
113 
114     /** The session ID */
115     private long sessionId;
116 
117     /**
118      * A future that will be set 'closed' when the connection is closed.
119      */
120     private final CloseFuture closeFuture = new DefaultCloseFuture(this);
121 
122     private volatile boolean closing;
123 
124     // traffic control
125     private boolean readSuspended = false;
126 
127     private boolean writeSuspended = false;
128 
129     // Status variables
130     private final AtomicBoolean scheduledForFlush = new AtomicBoolean();
131 
132     private final AtomicInteger scheduledWriteBytes = new AtomicInteger();
133 
134     private final AtomicInteger scheduledWriteMessages = new AtomicInteger();
135 
136     private long readBytes;
137 
138     private long writtenBytes;
139 
140     private long readMessages;
141 
142     private long writtenMessages;
143 
144     private long lastReadTime;
145 
146     private long lastWriteTime;
147 
148     private long lastThroughputCalculationTime;
149 
150     private long lastReadBytes;
151 
152     private long lastWrittenBytes;
153 
154     private long lastReadMessages;
155 
156     private long lastWrittenMessages;
157 
158     private double readBytesThroughput;
159 
160     private double writtenBytesThroughput;
161 
162     private double readMessagesThroughput;
163 
164     private double writtenMessagesThroughput;
165 
166     private AtomicInteger idleCountForBoth = new AtomicInteger();
167 
168     private AtomicInteger idleCountForRead = new AtomicInteger();
169 
170     private AtomicInteger idleCountForWrite = new AtomicInteger();
171 
172     private long lastIdleTimeForBoth;
173 
174     private long lastIdleTimeForRead;
175 
176     private long lastIdleTimeForWrite;
177 
178     private boolean deferDecreaseReadBuffer = true;
179 
180     /**
181      * TODO Add method documentation
182      */
183     protected AbstractIoSession(IoService service) {
184         this.service = service;
185         this.handler = service.getHandler();
186 
187         // Initialize all the Session counters to the current time
188         long currentTime = System.currentTimeMillis();
189         creationTime = currentTime;
190         lastThroughputCalculationTime = currentTime;
191         lastReadTime = currentTime;
192         lastWriteTime = currentTime;
193         lastIdleTimeForBoth = currentTime;
194         lastIdleTimeForRead = currentTime;
195         lastIdleTimeForWrite = currentTime;
196 
197         // TODO add documentation
198         closeFuture.addListener(SCHEDULED_COUNTER_RESETTER);
199 
200         // Set a new ID for this session
201         sessionId = idGenerator.incrementAndGet();
202     }
203 
204     /**
205      * {@inheritDoc}
206      *
207      * We use an AtomicLong to guarantee that the session ID are unique.
208      */
209     public final long getId() {
210         return sessionId;
211     }
212 
213     /**
214      * @return The associated IoProcessor for this session
215      */
216     public abstract IoProcessor getProcessor();
217 
218     /**
219      * {@inheritDoc}
220      */
221     public final boolean isConnected() {
222         return !closeFuture.isClosed();
223     }
224 
225     /**
226      * {@inheritDoc}
227      */
228     public final boolean isClosing() {
229         return closing || closeFuture.isClosed();
230     }
231 
232     /**
233      * {@inheritDoc}
234      */
235     public final CloseFuture getCloseFuture() {
236         return closeFuture;
237     }
238 
239     /**
240      * Tells if the session is scheduled for flushed
241      *
242      * @param true if the session is scheduled for flush
243      */
244     public final boolean isScheduledForFlush() {
245         return scheduledForFlush.get();
246     }
247 
248     /**
249      * Schedule the session for flushed
250      */
251     public final void scheduledForFlush() {
252         scheduledForFlush.set(true);
253     }
254 
255     /**
256      * Change the session's status : it's not anymore scheduled for flush
257      */
258     public final void unscheduledForFlush() {
259         scheduledForFlush.set(false);
260     }
261 
262     /**
263      * Set the scheduledForFLush flag. As we may have concurrent access to this
264      * flag, we compare and set it in one call.
265      *
266      * @param schedule
267      *            the new value to set if not already set.
268      * @return true if the session flag has been set, and if it wasn't set
269      *         already.
270      */
271     public final boolean setScheduledForFlush(boolean schedule) {
272         if (schedule) {
273             // If the current tag is set to false, switch it to true,
274             // otherwise, we do nothing but return false : the session
275             // is already scheduled for flush
276             return scheduledForFlush.compareAndSet(false, schedule);
277         }
278 
279         scheduledForFlush.set(schedule);
280         return true;
281     }
282 
283     /**
284      * {@inheritDoc}
285      */
286     public final CloseFuture close(boolean rightNow) {
287         if (rightNow) {
288             return close();
289         }
290 
291         return closeOnFlush();
292     }
293 
294     /**
295      * {@inheritDoc}
296      */
297     public final CloseFuture close() {
298         synchronized (lock) {
299             if (isClosing()) {
300                 return closeFuture;
301             }
302 
303             closing = true;
304         }
305 
306         getFilterChain().fireFilterClose();
307         return closeFuture;
308     }
309 
310     private final CloseFuture closeOnFlush() {
311         getWriteRequestQueue().offer(this, CLOSE_REQUEST);
312         getProcessor().flush(this);
313         return closeFuture;
314     }
315 
316     /**
317      * {@inheritDoc}
318      */
319     public IoHandler getHandler() {
320         return handler;
321     }
322 
323     /**
324      * {@inheritDoc}
325      */
326     public IoSessionConfig getConfig() {
327         return config;
328     }
329 
330     /**
331      * {@inheritDoc}
332      */
333     public final ReadFuture read() {
334         if (!getConfig().isUseReadOperation()) {
335             throw new IllegalStateException("useReadOperation is not enabled.");
336         }
337 
338         Queue<ReadFuture> readyReadFutures = getReadyReadFutures();
339         ReadFuture future;
340         synchronized (readyReadFutures) {
341             future = readyReadFutures.poll();
342             if (future != null) {
343                 if (future.isClosed()) {
344                     // Let other readers get notified.
345                     readyReadFutures.offer(future);
346                 }
347             } else {
348                 future = new DefaultReadFuture(this);
349                 getWaitingReadFutures().offer(future);
350             }
351         }
352 
353         return future;
354     }
355 
356     /**
357      * TODO Add method documentation
358      */
359     public final void offerReadFuture(Object message) {
360         newReadFuture().setRead(message);
361     }
362 
363     /**
364      * TODO Add method documentation
365      */
366     public final void offerFailedReadFuture(Throwable exception) {
367         newReadFuture().setException(exception);
368     }
369 
370     /**
371      * TODO Add method documentation
372      */
373     public final void offerClosedReadFuture() {
374         Queue<ReadFuture> readyReadFutures = getReadyReadFutures();
375         synchronized (readyReadFutures) {
376             newReadFuture().setClosed();
377         }
378     }
379 
380     /**
381      * TODO Add method documentation
382      */
383     private ReadFuture newReadFuture() {
384         Queue<ReadFuture> readyReadFutures = getReadyReadFutures();
385         Queue<ReadFuture> waitingReadFutures = getWaitingReadFutures();
386         ReadFuture future;
387         synchronized (readyReadFutures) {
388             future = waitingReadFutures.poll();
389             if (future == null) {
390                 future = new DefaultReadFuture(this);
391                 readyReadFutures.offer(future);
392             }
393         }
394         return future;
395     }
396 
397     /**
398      * TODO Add method documentation
399      */
400     private Queue<ReadFuture> getReadyReadFutures() {
401         Queue<ReadFuture> readyReadFutures = (Queue<ReadFuture>) getAttribute(READY_READ_FUTURES_KEY);
402         if (readyReadFutures == null) {
403             readyReadFutures = new ConcurrentLinkedQueue<ReadFuture>();
404 
405             Queue<ReadFuture> oldReadyReadFutures = (Queue<ReadFuture>) setAttributeIfAbsent(READY_READ_FUTURES_KEY,
406                     readyReadFutures);
407             if (oldReadyReadFutures != null) {
408                 readyReadFutures = oldReadyReadFutures;
409             }
410         }
411         return readyReadFutures;
412     }
413 
414     /**
415      * TODO Add method documentation
416      */
417     private Queue<ReadFuture> getWaitingReadFutures() {
418         Queue<ReadFuture> waitingReadyReadFutures = (Queue<ReadFuture>) getAttribute(WAITING_READ_FUTURES_KEY);
419         if (waitingReadyReadFutures == null) {
420             waitingReadyReadFutures = new ConcurrentLinkedQueue<ReadFuture>();
421 
422             Queue<ReadFuture> oldWaitingReadyReadFutures = (Queue<ReadFuture>) setAttributeIfAbsent(
423                     WAITING_READ_FUTURES_KEY, waitingReadyReadFutures);
424             if (oldWaitingReadyReadFutures != null) {
425                 waitingReadyReadFutures = oldWaitingReadyReadFutures;
426             }
427         }
428         return waitingReadyReadFutures;
429     }
430 
431     /**
432      * {@inheritDoc}
433      */
434     public WriteFuture write(Object message) {
435         return write(message, null);
436     }
437 
438     /**
439      * {@inheritDoc}
440      */
441     public WriteFuture write(Object message, SocketAddress remoteAddress) {
442         if (message == null) {
443             throw new IllegalArgumentException("message");
444         }
445 
446         // We can't send a message to a connected session if we don't have
447         // the remote address
448         if (!getTransportMetadata().isConnectionless() && ( remoteAddress != null )) {
449             throw new UnsupportedOperationException();
450         }
451 
452         // If the session has been closed or is closing, we can't either
453         // send a message to the remote side. We generate a future
454         // containing an exception.
455         if (isClosing() || !isConnected()) {
456             WriteFuture future = new DefaultWriteFuture(this);
457             WriteRequest request = new DefaultWriteRequest(message, future, remoteAddress);
458             WriteException writeException = new WriteToClosedSessionException(request);
459             future.setException(writeException);
460             return future;
461         }
462 
463         FileChannel openedFileChannel = null;
464 
465         // TODO: remove this code as soon as we use InputStream
466         // instead of Object for the message.
467         try {
468             if (( message instanceof IoBuffer ) && !((IoBuffer) message).hasRemaining()) {
469                 // Nothing to write : probably an error in the user code
470                 throw new IllegalArgumentException("message is empty. Forgot to call flip()?");
471             } else if (message instanceof FileChannel) {
472                 FileChannel fileChannel = (FileChannel) message;
473                 message = new DefaultFileRegion(fileChannel, 0, fileChannel.size());
474             } else if (message instanceof File) {
475                 File file = (File) message;
476                 openedFileChannel = new FileInputStream(file).getChannel();
477                 message = new FilenameFileRegion(file, openedFileChannel, 0, openedFileChannel.size());
478             }
479         } catch (IOException e) {
480             ExceptionMonitor.getInstance().exceptionCaught(e);
481             return DefaultWriteFuture.newNotWrittenFuture(this, e);
482         }
483 
484         // Now, we can write the message. First, create a future
485         WriteFuture writeFuture = new DefaultWriteFuture(this);
486         WriteRequest writeRequest = new DefaultWriteRequest(message, writeFuture, remoteAddress);
487 
488         // Then, get the chain and inject the WriteRequest into it
489         IoFilterChain filterChain = getFilterChain();
490         filterChain.fireFilterWrite(writeRequest);
491 
492         // TODO : This is not our business ! The caller has created a
493         // FileChannel,
494         // he has to close it !
495         if (openedFileChannel != null) {
496             // If we opened a FileChannel, it needs to be closed when the write
497             // has completed
498             final FileChannel finalChannel = openedFileChannel;
499             writeFuture.addListener(new IoFutureListener<WriteFuture>() {
500                 public void operationComplete(WriteFuture future) {
501                     try {
502                         finalChannel.close();
503                     } catch (IOException e) {
504                         ExceptionMonitor.getInstance().exceptionCaught(e);
505                     }
506                 }
507             });
508         }
509 
510         // Return the WriteFuture.
511         return writeFuture;
512     }
513 
514     /**
515      * {@inheritDoc}
516      */
517     public final Object getAttachment() {
518         return getAttribute("");
519     }
520 
521     /**
522      * {@inheritDoc}
523      */
524     public final Object setAttachment(Object attachment) {
525         return setAttribute("", attachment);
526     }
527 
528     /**
529      * {@inheritDoc}
530      */
531     public final Object getAttribute(Object key) {
532         return getAttribute(key, null);
533     }
534 
535     /**
536      * {@inheritDoc}
537      */
538     public final Object getAttribute(Object key, Object defaultValue) {
539         return attributes.getAttribute(this, key, defaultValue);
540     }
541 
542     /**
543      * {@inheritDoc}
544      */
545     public final Object setAttribute(Object key, Object value) {
546         return attributes.setAttribute(this, key, value);
547     }
548 
549     /**
550      * {@inheritDoc}
551      */
552     public final Object setAttribute(Object key) {
553         return setAttribute(key, Boolean.TRUE);
554     }
555 
556     /**
557      * {@inheritDoc}
558      */
559     public final Object setAttributeIfAbsent(Object key, Object value) {
560         return attributes.setAttributeIfAbsent(this, key, value);
561     }
562 
563     /**
564      * {@inheritDoc}
565      */
566     public final Object setAttributeIfAbsent(Object key) {
567         return setAttributeIfAbsent(key, Boolean.TRUE);
568     }
569 
570     /**
571      * {@inheritDoc}
572      */
573     public final Object removeAttribute(Object key) {
574         return attributes.removeAttribute(this, key);
575     }
576 
577     /**
578      * {@inheritDoc}
579      */
580     public final boolean removeAttribute(Object key, Object value) {
581         return attributes.removeAttribute(this, key, value);
582     }
583 
584     /**
585      * {@inheritDoc}
586      */
587     public final boolean replaceAttribute(Object key, Object oldValue, Object newValue) {
588         return attributes.replaceAttribute(this, key, oldValue, newValue);
589     }
590 
591     /**
592      * {@inheritDoc}
593      */
594     public final boolean containsAttribute(Object key) {
595         return attributes.containsAttribute(this, key);
596     }
597 
598     /**
599      * {@inheritDoc}
600      */
601     public final Set<Object> getAttributeKeys() {
602         return attributes.getAttributeKeys(this);
603     }
604 
605     /**
606      * TODO Add method documentation
607      */
608     public final IoSessionAttributeMap getAttributeMap() {
609         return attributes;
610     }
611 
612     /**
613      * TODO Add method documentation
614      */
615     public final void setAttributeMap(IoSessionAttributeMap attributes) {
616         this.attributes = attributes;
617     }
618 
619     /**
620      * Create a new close aware write queue, based on the given write queue.
621      *
622      * @param writeRequestQueue
623      *            The write request queue
624      */
625     public final void setWriteRequestQueue(WriteRequestQueue writeRequestQueue) {
626         this.writeRequestQueue = new CloseAwareWriteQueue(writeRequestQueue);
627     }
628 
629     /**
630      * {@inheritDoc}
631      */
632     public final void suspendRead() {
633         readSuspended = true;
634         if (isClosing() || !isConnected()) {
635             return;
636         }
637         getProcessor().updateTrafficControl(this);
638     }
639 
640     /**
641      * {@inheritDoc}
642      */
643     public final void suspendWrite() {
644         writeSuspended = true;
645         if (isClosing() || !isConnected()) {
646             return;
647         }
648         getProcessor().updateTrafficControl(this);
649     }
650 
651     /**
652      * {@inheritDoc}
653      */
654     @SuppressWarnings("unchecked")
655     public final void resumeRead() {
656         readSuspended = false;
657         if (isClosing() || !isConnected()) {
658             return;
659         }
660         getProcessor().updateTrafficControl(this);
661     }
662 
663     /**
664      * {@inheritDoc}
665      */
666     @SuppressWarnings("unchecked")
667     public final void resumeWrite() {
668         writeSuspended = false;
669         if (isClosing() || !isConnected()) {
670             return;
671         }
672         getProcessor().updateTrafficControl(this);
673     }
674 
675     /**
676      * {@inheritDoc}
677      */
678     public boolean isReadSuspended() {
679         return readSuspended;
680     }
681 
682     /**
683      * {@inheritDoc}
684      */
685     public boolean isWriteSuspended() {
686         return writeSuspended;
687     }
688 
689     /**
690      * {@inheritDoc}
691      */
692     public final long getReadBytes() {
693         return readBytes;
694     }
695 
696     /**
697      * {@inheritDoc}
698      */
699     public final long getWrittenBytes() {
700         return writtenBytes;
701     }
702 
703     /**
704      * {@inheritDoc}
705      */
706     public final long getReadMessages() {
707         return readMessages;
708     }
709 
710     /**
711      * {@inheritDoc}
712      */
713     public final long getWrittenMessages() {
714         return writtenMessages;
715     }
716 
717     /**
718      * {@inheritDoc}
719      */
720     public final double getReadBytesThroughput() {
721         return readBytesThroughput;
722     }
723 
724     /**
725      * {@inheritDoc}
726      */
727     public final double getWrittenBytesThroughput() {
728         return writtenBytesThroughput;
729     }
730 
731     /**
732      * {@inheritDoc}
733      */
734     public final double getReadMessagesThroughput() {
735         return readMessagesThroughput;
736     }
737 
738     /**
739      * {@inheritDoc}
740      */
741     public final double getWrittenMessagesThroughput() {
742         return writtenMessagesThroughput;
743     }
744 
745     /**
746      * {@inheritDoc}
747      */
748     public final void updateThroughput(long currentTime, boolean force) {
749         int interval = (int) (currentTime - lastThroughputCalculationTime);
750 
751         long minInterval = getConfig().getThroughputCalculationIntervalInMillis();
752         if (( minInterval == 0 ) || ( interval < minInterval )) {
753             if (!force) {
754                 return;
755             }
756         }
757 
758         readBytesThroughput = (readBytes - lastReadBytes) * 1000.0 / interval;
759         writtenBytesThroughput = (writtenBytes - lastWrittenBytes) * 1000.0 / interval;
760         readMessagesThroughput = (readMessages - lastReadMessages) * 1000.0 / interval;
761         writtenMessagesThroughput = (writtenMessages - lastWrittenMessages) * 1000.0 / interval;
762 
763         lastReadBytes = readBytes;
764         lastWrittenBytes = writtenBytes;
765         lastReadMessages = readMessages;
766         lastWrittenMessages = writtenMessages;
767 
768         lastThroughputCalculationTime = currentTime;
769     }
770 
771     /**
772      * {@inheritDoc}
773      */
774     public final long getScheduledWriteBytes() {
775         return scheduledWriteBytes.get();
776     }
777 
778     /**
779      * {@inheritDoc}
780      */
781     public final int getScheduledWriteMessages() {
782         return scheduledWriteMessages.get();
783     }
784 
785     /**
786      * TODO Add method documentation
787      */
788     protected void setScheduledWriteBytes(int byteCount) {
789         scheduledWriteBytes.set(byteCount);
790     }
791 
792     /**
793      * TODO Add method documentation
794      */
795     protected void setScheduledWriteMessages(int messages) {
796         scheduledWriteMessages.set(messages);
797     }
798 
799     /**
800      * TODO Add method documentation
801      */
802     public final void increaseReadBytes(long increment, long currentTime) {
803         if (increment <= 0) {
804             return;
805         }
806 
807         readBytes += increment;
808         lastReadTime = currentTime;
809         idleCountForBoth.set(0);
810         idleCountForRead.set(0);
811 
812         if (getService() instanceof AbstractIoService) {
813             ((AbstractIoService) getService()).getStatistics().increaseReadBytes(increment, currentTime);
814         }
815     }
816 
817     /**
818      * TODO Add method documentation
819      */
820     public final void increaseReadMessages(long currentTime) {
821         readMessages++;
822         lastReadTime = currentTime;
823         idleCountForBoth.set(0);
824         idleCountForRead.set(0);
825 
826         if (getService() instanceof AbstractIoService) {
827             ((AbstractIoService) getService()).getStatistics().increaseReadMessages(currentTime);
828         }
829     }
830 
831     /**
832      * TODO Add method documentation
833      */
834     public final void increaseWrittenBytes(int increment, long currentTime) {
835         if (increment <= 0) {
836             return;
837         }
838 
839         writtenBytes += increment;
840         lastWriteTime = currentTime;
841         idleCountForBoth.set(0);
842         idleCountForWrite.set(0);
843 
844         if (getService() instanceof AbstractIoService) {
845             ((AbstractIoService) getService()).getStatistics().increaseWrittenBytes(increment, currentTime);
846         }
847 
848         increaseScheduledWriteBytes(-increment);
849     }
850 
851     /**
852      * TODO Add method documentation
853      */
854     public final void increaseWrittenMessages(WriteRequest request, long currentTime) {
855         Object message = request.getMessage();
856         if (message instanceof IoBuffer) {
857             IoBuffer b = (IoBuffer) message;
858             if (b.hasRemaining()) {
859                 return;
860             }
861         }
862 
863         writtenMessages++;
864         lastWriteTime = currentTime;
865         if (getService() instanceof AbstractIoService) {
866             ((AbstractIoService) getService()).getStatistics().increaseWrittenMessages(currentTime);
867         }
868 
869         decreaseScheduledWriteMessages();
870     }
871 
872     /**
873      * TODO Add method documentation
874      */
875     public final void increaseScheduledWriteBytes(int increment) {
876         scheduledWriteBytes.addAndGet(increment);
877         if (getService() instanceof AbstractIoService) {
878             ((AbstractIoService) getService()).getStatistics().increaseScheduledWriteBytes(increment);
879         }
880     }
881 
882     /**
883      * TODO Add method documentation
884      */
885     public final void increaseScheduledWriteMessages() {
886         scheduledWriteMessages.incrementAndGet();
887         if (getService() instanceof AbstractIoService) {
888             ((AbstractIoService) getService()).getStatistics().increaseScheduledWriteMessages();
889         }
890     }
891 
892     /**
893      * TODO Add method documentation
894      */
895     private void decreaseScheduledWriteMessages() {
896         scheduledWriteMessages.decrementAndGet();
897         if (getService() instanceof AbstractIoService) {
898             ((AbstractIoService) getService()).getStatistics().decreaseScheduledWriteMessages();
899         }
900     }
901 
902     /**
903      * TODO Add method documentation
904      */
905     public final void decreaseScheduledBytesAndMessages(WriteRequest request) {
906         Object message = request.getMessage();
907         if (message instanceof IoBuffer) {
908             IoBuffer b = (IoBuffer) message;
909             if (b.hasRemaining()) {
910                 increaseScheduledWriteBytes(-((IoBuffer) message).remaining());
911             } else {
912                 decreaseScheduledWriteMessages();
913             }
914         } else {
915             decreaseScheduledWriteMessages();
916         }
917     }
918 
919     /**
920      * {@inheritDoc}
921      */
922     public final WriteRequestQueue getWriteRequestQueue() {
923         if (writeRequestQueue == null) {
924             throw new IllegalStateException();
925         }
926         return writeRequestQueue;
927     }
928 
929     /**
930      * {@inheritDoc}
931      */
932     public final WriteRequest getCurrentWriteRequest() {
933         return currentWriteRequest;
934     }
935 
936     /**
937      * {@inheritDoc}
938      */
939     public final Object getCurrentWriteMessage() {
940         WriteRequest req = getCurrentWriteRequest();
941         if (req == null) {
942             return null;
943         }
944         return req.getMessage();
945     }
946 
947     /**
948      * {@inheritDoc}
949      */
950     public final void setCurrentWriteRequest(WriteRequest currentWriteRequest) {
951         this.currentWriteRequest = currentWriteRequest;
952     }
953 
954     /**
955      * TODO Add method documentation
956      */
957     public final void increaseReadBufferSize() {
958         int newReadBufferSize = getConfig().getReadBufferSize() << 1;
959         if (newReadBufferSize <= getConfig().getMaxReadBufferSize()) {
960             getConfig().setReadBufferSize(newReadBufferSize);
961         } else {
962             getConfig().setReadBufferSize(getConfig().getMaxReadBufferSize());
963         }
964 
965         deferDecreaseReadBuffer = true;
966     }
967 
968     /**
969      * TODO Add method documentation
970      */
971     public final void decreaseReadBufferSize() {
972         if (deferDecreaseReadBuffer) {
973             deferDecreaseReadBuffer = false;
974             return;
975         }
976 
977         if (getConfig().getReadBufferSize() > getConfig().getMinReadBufferSize()) {
978             getConfig().setReadBufferSize(getConfig().getReadBufferSize() >>> 1);
979         }
980 
981         deferDecreaseReadBuffer = true;
982     }
983 
984     /**
985      * {@inheritDoc}
986      */
987     public final long getCreationTime() {
988         return creationTime;
989     }
990 
991     /**
992      * {@inheritDoc}
993      */
994     public final long getLastIoTime() {
995         return Math.max(lastReadTime, lastWriteTime);
996     }
997 
998     /**
999      * {@inheritDoc}
1000      */
1001     public final long getLastReadTime() {
1002         return lastReadTime;
1003     }
1004 
1005     /**
1006      * {@inheritDoc}
1007      */
1008     public final long getLastWriteTime() {
1009         return lastWriteTime;
1010     }
1011 
1012     /**
1013      * {@inheritDoc}
1014      */
1015     public final boolean isIdle(IdleStatus status) {
1016         if (status == IdleStatus.BOTH_IDLE) {
1017             return idleCountForBoth.get() > 0;
1018         }
1019 
1020         if (status == IdleStatus.READER_IDLE) {
1021             return idleCountForRead.get() > 0;
1022         }
1023 
1024         if (status == IdleStatus.WRITER_IDLE) {
1025             return idleCountForWrite.get() > 0;
1026         }
1027 
1028         throw new IllegalArgumentException("Unknown idle status: " + status);
1029     }
1030 
1031     /**
1032      * {@inheritDoc}
1033      */
1034     public final boolean isBothIdle() {
1035         return isIdle(IdleStatus.BOTH_IDLE);
1036     }
1037 
1038     /**
1039      * {@inheritDoc}
1040      */
1041     public final boolean isReaderIdle() {
1042         return isIdle(IdleStatus.READER_IDLE);
1043     }
1044 
1045     /**
1046      * {@inheritDoc}
1047      */
1048     public final boolean isWriterIdle() {
1049         return isIdle(IdleStatus.WRITER_IDLE);
1050     }
1051 
1052     /**
1053      * {@inheritDoc}
1054      */
1055     public final int getIdleCount(IdleStatus status) {
1056         if (getConfig().getIdleTime(status) == 0) {
1057             if (status == IdleStatus.BOTH_IDLE) {
1058                 idleCountForBoth.set(0);
1059             }
1060 
1061             if (status == IdleStatus.READER_IDLE) {
1062                 idleCountForRead.set(0);
1063             }
1064 
1065             if (status == IdleStatus.WRITER_IDLE) {
1066                 idleCountForWrite.set(0);
1067             }
1068         }
1069 
1070         if (status == IdleStatus.BOTH_IDLE) {
1071             return idleCountForBoth.get();
1072         }
1073 
1074         if (status == IdleStatus.READER_IDLE) {
1075             return idleCountForRead.get();
1076         }
1077 
1078         if (status == IdleStatus.WRITER_IDLE) {
1079             return idleCountForWrite.get();
1080         }
1081 
1082         throw new IllegalArgumentException("Unknown idle status: " + status);
1083     }
1084 
1085     /**
1086      * {@inheritDoc}
1087      */
1088     public final long getLastIdleTime(IdleStatus status) {
1089         if (status == IdleStatus.BOTH_IDLE) {
1090             return lastIdleTimeForBoth;
1091         }
1092 
1093         if (status == IdleStatus.READER_IDLE) {
1094             return lastIdleTimeForRead;
1095         }
1096 
1097         if (status == IdleStatus.WRITER_IDLE) {
1098             return lastIdleTimeForWrite;
1099         }
1100 
1101         throw new IllegalArgumentException("Unknown idle status: " + status);
1102     }
1103 
1104     /**
1105      * TODO Add method documentation
1106      */
1107     public final void increaseIdleCount(IdleStatus status, long currentTime) {
1108         if (status == IdleStatus.BOTH_IDLE) {
1109             idleCountForBoth.incrementAndGet();
1110             lastIdleTimeForBoth = currentTime;
1111         } else if (status == IdleStatus.READER_IDLE) {
1112             idleCountForRead.incrementAndGet();
1113             lastIdleTimeForRead = currentTime;
1114         } else if (status == IdleStatus.WRITER_IDLE) {
1115             idleCountForWrite.incrementAndGet();
1116             lastIdleTimeForWrite = currentTime;
1117         } else {
1118             throw new IllegalArgumentException("Unknown idle status: " + status);
1119         }
1120     }
1121 
1122     /**
1123      * {@inheritDoc}
1124      */
1125     public final int getBothIdleCount() {
1126         return getIdleCount(IdleStatus.BOTH_IDLE);
1127     }
1128 
1129     /**
1130      * {@inheritDoc}
1131      */
1132     public final long getLastBothIdleTime() {
1133         return getLastIdleTime(IdleStatus.BOTH_IDLE);
1134     }
1135 
1136     /**
1137      * {@inheritDoc}
1138      */
1139     public final long getLastReaderIdleTime() {
1140         return getLastIdleTime(IdleStatus.READER_IDLE);
1141     }
1142 
1143     /**
1144      * {@inheritDoc}
1145      */
1146     public final long getLastWriterIdleTime() {
1147         return getLastIdleTime(IdleStatus.WRITER_IDLE);
1148     }
1149 
1150     /**
1151      * {@inheritDoc}
1152      */
1153     public final int getReaderIdleCount() {
1154         return getIdleCount(IdleStatus.READER_IDLE);
1155     }
1156 
1157     /**
1158      * {@inheritDoc}
1159      */
1160     public final int getWriterIdleCount() {
1161         return getIdleCount(IdleStatus.WRITER_IDLE);
1162     }
1163 
1164     /**
1165      * {@inheritDoc}
1166      */
1167     public SocketAddress getServiceAddress() {
1168         IoService service = getService();
1169         if (service instanceof IoAcceptor) {
1170             return ((IoAcceptor) service).getLocalAddress();
1171         }
1172 
1173         return getRemoteAddress();
1174     }
1175 
1176     /**
1177      * {@inheritDoc}
1178      */
1179     @Override
1180     public final int hashCode() {
1181         return super.hashCode();
1182     }
1183 
1184     /**
1185      * {@inheritDoc} TODO This is a ridiculous implementation. Need to be
1186      * replaced.
1187      */
1188     @Override
1189     public final boolean equals(Object o) {
1190         return super.equals(o);
1191     }
1192 
1193     /**
1194      * {@inheritDoc}
1195      */
1196     @Override
1197     public String toString() {
1198         if (isConnected() || isClosing()) {
1199             String remote = null;
1200             String local = null;
1201 
1202             try {
1203                 remote = String.valueOf(getRemoteAddress());
1204             } catch ( Throwable t ) {
1205                 remote = "Cannot get the remote address informations: " + t.getMessage();
1206             }
1207 
1208             try {
1209                 local = String.valueOf(getLocalAddress());
1210             } catch ( Throwable t ) {
1211                 local = "Cannot get the local address informations: " + t.getMessage();
1212             }
1213 
1214             if (getService() instanceof IoAcceptor) {
1215                 return "(" + getIdAsString() + ": " + getServiceName() + ", server, " + remote + " => " + local
1216                         + ')';
1217             }
1218 
1219             return "(" + getIdAsString() + ": " + getServiceName() + ", client, " + local + " => " + remote + ')';
1220         }
1221 
1222         return "(" + getIdAsString() + ") Session disconnected ...";
1223     }
1224 
1225     /**
1226      * TODO Add method documentation
1227      */
1228     private String getIdAsString() {
1229         String id = Long.toHexString(getId()).toUpperCase();
1230 
1231         // Somewhat inefficient, but it won't happen that often
1232         // because an ID is often a big integer.
1233         while (id.length() < 8) {
1234             id = '0' + id; // padding
1235         }
1236         id = "0x" + id;
1237 
1238         return id;
1239     }
1240 
1241     /**
1242      * TODO Add method documentation
1243      */
1244     private String getServiceName() {
1245         TransportMetadata tm = getTransportMetadata();
1246         if (tm == null) {
1247             return "null";
1248         }
1249 
1250         return tm.getProviderName() + ' ' + tm.getName();
1251     }
1252 
1253     /**
1254      * {@inheritDoc}
1255      */
1256     public IoService getService() {
1257         return service;
1258     }
1259 
1260     /**
1261      * Fires a {@link IoEventType#SESSION_IDLE} event to any applicable sessions
1262      * in the specified collection.
1263      *
1264      * @param currentTime
1265      *            the current time (i.e. {@link System#currentTimeMillis()})
1266      */
1267     public static void notifyIdleness(Iterator<? extends IoSession> sessions, long currentTime) {
1268         IoSession s = null;
1269         while (sessions.hasNext()) {
1270             s = sessions.next();
1271             notifyIdleSession(s, currentTime);
1272         }
1273     }
1274 
1275     /**
1276      * Fires a {@link IoEventType#SESSION_IDLE} event if applicable for the
1277      * specified {@code session}.
1278      *
1279      * @param currentTime
1280      *            the current time (i.e. {@link System#currentTimeMillis()})
1281      */
1282     public static void notifyIdleSession(IoSession session, long currentTime) {
1283         notifyIdleSession0(session, currentTime, session.getConfig().getIdleTimeInMillis(IdleStatus.BOTH_IDLE),
1284                 IdleStatus.BOTH_IDLE, Math.max(session.getLastIoTime(), session.getLastIdleTime(IdleStatus.BOTH_IDLE)));
1285 
1286         notifyIdleSession0(session, currentTime, session.getConfig().getIdleTimeInMillis(IdleStatus.READER_IDLE),
1287                 IdleStatus.READER_IDLE, Math.max(session.getLastReadTime(), session
1288                         .getLastIdleTime(IdleStatus.READER_IDLE)));
1289 
1290         notifyIdleSession0(session, currentTime, session.getConfig().getIdleTimeInMillis(IdleStatus.WRITER_IDLE),
1291                 IdleStatus.WRITER_IDLE, Math.max(session.getLastWriteTime(), session
1292                         .getLastIdleTime(IdleStatus.WRITER_IDLE)));
1293 
1294         notifyWriteTimeout(session, currentTime);
1295     }
1296 
1297     private static void notifyIdleSession0(IoSession session, long currentTime, long idleTime, IdleStatus status,
1298             long lastIoTime) {
1299         if (( idleTime > 0 ) && ( lastIoTime != 0 ) && ( currentTime - lastIoTime >= idleTime )) {
1300             session.getFilterChain().fireSessionIdle(status);
1301         }
1302     }
1303 
1304     private static void notifyWriteTimeout(IoSession session, long currentTime) {
1305 
1306         long writeTimeout = session.getConfig().getWriteTimeoutInMillis();
1307         if (( writeTimeout > 0 ) && ( currentTime - session.getLastWriteTime() >= writeTimeout )
1308                 && !session.getWriteRequestQueue().isEmpty(session)) {
1309             WriteRequest request = session.getCurrentWriteRequest();
1310             if (request != null) {
1311                 session.setCurrentWriteRequest(null);
1312                 WriteTimeoutException cause = new WriteTimeoutException(request);
1313                 request.getFuture().setException(cause);
1314                 session.getFilterChain().fireExceptionCaught(cause);
1315                 // WriteException is an IOException, so we close the session.
1316                 session.close(true);
1317             }
1318         }
1319     }
1320 
1321     /**
1322      * A queue which handles the CLOSE request.
1323      *
1324      * TODO : Check that when closing a session, all the pending requests are
1325      * correctly sent.
1326      */
1327     private class CloseAwareWriteQueue implements WriteRequestQueue {
1328 
1329         private final WriteRequestQueue queue;
1330 
1331         /**
1332          * {@inheritDoc}
1333          */
1334         public CloseAwareWriteQueue(WriteRequestQueue queue) {
1335             this.queue = queue;
1336         }
1337 
1338         /**
1339          * {@inheritDoc}
1340          */
1341         public synchronized WriteRequest poll(IoSession session) {
1342             WriteRequest answer = queue.poll(session);
1343 
1344             if (answer == CLOSE_REQUEST) {
1345                 AbstractIoSession.this.close();
1346                 dispose(session);
1347                 answer = null;
1348             }
1349 
1350             return answer;
1351         }
1352 
1353         /**
1354          * {@inheritDoc}
1355          */
1356         public void offer(IoSession session, WriteRequest e) {
1357             queue.offer(session, e);
1358         }
1359 
1360         /**
1361          * {@inheritDoc}
1362          */
1363         public boolean isEmpty(IoSession session) {
1364             return queue.isEmpty(session);
1365         }
1366 
1367         /**
1368          * {@inheritDoc}
1369          */
1370         public void clear(IoSession session) {
1371             queue.clear(session);
1372         }
1373 
1374         /**
1375          * {@inheritDoc}
1376          */
1377         public void dispose(IoSession session) {
1378             queue.dispose(session);
1379         }
1380     }
1381 }