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