View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.io;
20  
21  import java.util.concurrent.TimeUnit;
22  import java.util.concurrent.TimeoutException;
23  import java.util.concurrent.atomic.AtomicReference;
24  
25  import org.eclipse.jetty.util.log.Log;
26  import org.eclipse.jetty.util.log.Logger;
27  import org.eclipse.jetty.util.thread.Scheduler;
28  
29  
30  
31  /* ------------------------------------------------------------ */
32  /** An Abstract implementation of an Idle Timeout.
33   *
34   * This implementation is optimised that timeout operations are not cancelled on
35   * every operation. Rather timeout are allowed to expire and a check is then made
36   * to see when the last operation took place.  If the idle timeout has not expired,
37   * the timeout is rescheduled for the earliest possible time a timeout could occur.
38   *
39   */
40  public abstract class IdleTimeout
41  {
42      private static final Logger LOG = Log.getLogger(IdleTimeout.class);
43      private final Scheduler _scheduler;
44      private final AtomicReference<Scheduler.Task> _timeout = new AtomicReference<>();
45      private volatile long _idleTimeout;
46      private volatile long _idleTimestamp=System.currentTimeMillis();
47  
48      private final Runnable _idleTask = new Runnable()
49      {
50          @Override
51          public void run()
52          {
53              long idleLeft=checkIdleTimeout();
54              if (idleLeft>=0)
55                  scheduleIdleTimeout(idleLeft > 0 ? idleLeft : getIdleTimeout());
56          }
57      };
58  
59      /**
60       * @param scheduler A scheduler used to schedule checks for the idle timeout.
61       */
62      public IdleTimeout(Scheduler scheduler)
63      {
64          _scheduler=scheduler;
65      }
66  
67      public long getIdleTimestamp()
68      {
69          return _idleTimestamp;
70      }
71  
72      public long getIdleTimeout()
73      {
74          return _idleTimeout;
75      }
76  
77      public void setIdleTimeout(long idleTimeout)
78      {
79          long old=_idleTimeout;
80          _idleTimeout = idleTimeout;
81  
82          // Do we have an old timeout
83          if (old>0)
84          {
85              // if the old was less than or equal to the new timeout, then nothing more to do
86              if (old<=idleTimeout)
87                  return;
88  
89              // old timeout is too long, so cancel it.
90              Scheduler.Task oldTimeout = _timeout.getAndSet(null);
91              if (oldTimeout != null)
92                  oldTimeout.cancel();
93          }
94  
95          // If we have a new timeout, then check and reschedule
96          if (idleTimeout>0 && isOpen())
97              _idleTask.run();
98      }
99  
100     /** This method should be called when non-idle activity has taken place.
101      */
102     public void notIdle()
103     {
104         _idleTimestamp=System.currentTimeMillis();
105     }
106 
107     private void scheduleIdleTimeout(long delay)
108     {
109         Scheduler.Task newTimeout = null;
110         if (isOpen() && delay > 0 && _scheduler!=null)
111             newTimeout = _scheduler.schedule(_idleTask, delay, TimeUnit.MILLISECONDS);
112         Scheduler.Task oldTimeout = _timeout.getAndSet(newTimeout);
113         if (oldTimeout != null)
114             oldTimeout.cancel();
115     }
116 
117     public void onOpen()
118     {
119         if (_idleTimeout>0)
120             _idleTask.run();
121     }
122 
123     protected void close()
124     {
125         Scheduler.Task oldTimeout = _timeout.getAndSet(null);
126         if (oldTimeout != null)
127             oldTimeout.cancel();
128     }
129 
130     protected long checkIdleTimeout()
131     {
132         if (isOpen())
133         {
134             long idleTimestamp = getIdleTimestamp();
135             long idleTimeout = getIdleTimeout();
136             long idleElapsed = System.currentTimeMillis() - idleTimestamp;
137             long idleLeft = idleTimeout - idleElapsed;
138 
139             LOG.debug("{} idle timeout check, elapsed: {} ms, remaining: {} ms", this, idleElapsed, idleLeft);
140 
141             if (idleTimestamp != 0 && idleTimeout > 0)
142             {
143                 if (idleLeft <= 0)
144                 {
145                     LOG.debug("{} idle timeout expired", this);
146                     try
147                     {
148                         onIdleExpired(new TimeoutException("Idle timeout expired: " + idleElapsed + "/" + idleTimeout + " ms"));
149                     }
150                     finally
151                     {
152                         notIdle();
153                     }
154                 }
155             }
156 
157             return idleLeft>=0?idleLeft:0;
158         }
159         return -1;
160     }
161 
162     /* ------------------------------------------------------------ */
163     /** This abstract method is called when the idle timeout has expired.
164      * @param timeout a TimeoutException
165      */
166     abstract protected void onIdleExpired(TimeoutException timeout);
167 
168 
169     /* ------------------------------------------------------------ */
170     /** This abstract method should be called to check if idle timeouts
171      * should still be checked.
172      * @return True if the entity monitored should still be checked for idle timeouts
173      */
174     abstract protected boolean isOpen();
175 }