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