View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2015 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   * An Abstract implementation of an Idle Timeout.
31   * <p/>
32   * This implementation is optimised that timeout operations are not cancelled on
33   * every operation. Rather timeout are allowed to expire and a check is then made
34   * to see when the last operation took place.  If the idle timeout has not expired,
35   * the timeout is rescheduled for the earliest possible time a timeout could occur.
36   */
37  public abstract class IdleTimeout
38  {
39      private static final Logger LOG = Log.getLogger(IdleTimeout.class);
40      private final Scheduler _scheduler;
41      private final AtomicReference<Scheduler.Task> _timeout = new AtomicReference<>();
42      private volatile long _idleTimeout;
43      private volatile long _idleTimestamp = System.currentTimeMillis();
44  
45      private final Runnable _idleTask = new Runnable()
46      {
47          @Override
48          public void run()
49          {
50              long idleLeft = checkIdleTimeout();
51              if (idleLeft >= 0)
52                  scheduleIdleTimeout(idleLeft > 0 ? idleLeft : getIdleTimeout());
53          }
54      };
55  
56      /**
57       * @param scheduler A scheduler used to schedule checks for the idle timeout.
58       */
59      public IdleTimeout(Scheduler scheduler)
60      {
61          _scheduler = scheduler;
62      }
63  
64      public long getIdleTimestamp()
65      {
66          return _idleTimestamp;
67      }
68  
69      public long getIdleFor()
70      {
71          return System.currentTimeMillis() - getIdleTimestamp();
72      }
73  
74      public long getIdleTimeout()
75      {
76          return _idleTimeout;
77      }
78  
79      public void setIdleTimeout(long idleTimeout)
80      {
81          long old = _idleTimeout;
82          _idleTimeout = idleTimeout;
83  
84          // Do we have an old timeout
85          if (old > 0)
86          {
87              // if the old was less than or equal to the new timeout, then nothing more to do
88              if (old <= idleTimeout)
89                  return;
90  
91              // old timeout is too long, so cancel it.
92              deactivate();
93          }
94  
95          // If we have a new timeout, then check and reschedule
96          if (isOpen())
97              activate();
98      }
99  
100     /**
101      * This method should be called when non-idle activity has taken place.
102      */
103     public void notIdle()
104     {
105         _idleTimestamp = System.currentTimeMillis();
106     }
107 
108     private void scheduleIdleTimeout(long delay)
109     {
110         Scheduler.Task newTimeout = null;
111         if (isOpen() && delay > 0 && _scheduler != null)
112             newTimeout = _scheduler.schedule(_idleTask, delay, TimeUnit.MILLISECONDS);
113         Scheduler.Task oldTimeout = _timeout.getAndSet(newTimeout);
114         if (oldTimeout != null)
115             oldTimeout.cancel();
116     }
117 
118     public void onOpen()
119     {
120         activate();
121     }
122 
123     private void activate()
124     {
125         if (_idleTimeout > 0)
126             _idleTask.run();
127     }
128 
129     public void onClose()
130     {
131         deactivate();
132     }
133 
134     private void deactivate()
135     {
136         Scheduler.Task oldTimeout = _timeout.getAndSet(null);
137         if (oldTimeout != null)
138             oldTimeout.cancel();
139     }
140 
141     protected long checkIdleTimeout()
142     {
143         if (isOpen())
144         {
145             long idleTimestamp = getIdleTimestamp();
146             long idleTimeout = getIdleTimeout();
147             long idleElapsed = System.currentTimeMillis() - idleTimestamp;
148             long idleLeft = idleTimeout - idleElapsed;
149 
150             if (LOG.isDebugEnabled())
151                 LOG.debug("{} idle timeout check, elapsed: {} ms, remaining: {} ms", this, idleElapsed, idleLeft);
152 
153             if (idleTimestamp != 0 && idleTimeout > 0)
154             {
155                 if (idleLeft <= 0)
156                 {
157                     if (LOG.isDebugEnabled())
158                         LOG.debug("{} idle timeout expired", this);
159                     try
160                     {
161                         onIdleExpired(new TimeoutException("Idle timeout expired: " + idleElapsed + "/" + idleTimeout + " ms"));
162                     }
163                     finally
164                     {
165                         notIdle();
166                     }
167                 }
168             }
169 
170             return idleLeft >= 0 ? idleLeft : 0;
171         }
172         return -1;
173     }
174 
175     /**
176      * This abstract method is called when the idle timeout has expired.
177      *
178      * @param timeout a TimeoutException
179      */
180     protected abstract void onIdleExpired(TimeoutException timeout);
181 
182     /**
183      * This abstract method should be called to check if idle timeouts
184      * should still be checked.
185      *
186      * @return True if the entity monitored should still be checked for idle timeouts
187      */
188     public abstract boolean isOpen();
189 }