View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 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 Scheduler getScheduler()
65      {
66          return _scheduler;
67      }
68      
69      public long getIdleTimestamp()
70      {
71          return _idleTimestamp;
72      }
73  
74      public long getIdleFor()
75      {
76          return System.currentTimeMillis() - getIdleTimestamp();
77      }
78  
79      public long getIdleTimeout()
80      {
81          return _idleTimeout;
82      }
83  
84      public void setIdleTimeout(long idleTimeout)
85      {
86          long old = _idleTimeout;
87          _idleTimeout = idleTimeout;
88  
89          // Do we have an old timeout
90          if (old > 0)
91          {
92              // if the old was less than or equal to the new timeout, then nothing more to do
93              if (old <= idleTimeout)
94                  return;
95  
96              // old timeout is too long, so cancel it.
97              deactivate();
98          }
99  
100         // If we have a new timeout, then check and reschedule
101         if (isOpen())
102             activate();
103     }
104 
105     /**
106      * This method should be called when non-idle activity has taken place.
107      */
108     public void notIdle()
109     {
110         _idleTimestamp = System.currentTimeMillis();
111     }
112 
113     private void scheduleIdleTimeout(long delay)
114     {
115         Scheduler.Task newTimeout = null;
116         if (isOpen() && delay > 0 && _scheduler != null)
117             newTimeout = _scheduler.schedule(_idleTask, delay, TimeUnit.MILLISECONDS);
118         Scheduler.Task oldTimeout = _timeout.getAndSet(newTimeout);
119         if (oldTimeout != null)
120             oldTimeout.cancel();
121     }
122 
123     public void onOpen()
124     {
125         activate();
126     }
127 
128     private void activate()
129     {
130         if (_idleTimeout > 0)
131             _idleTask.run();
132     }
133 
134     public void onClose()
135     {
136         deactivate();
137     }
138 
139     private void deactivate()
140     {
141         Scheduler.Task oldTimeout = _timeout.getAndSet(null);
142         if (oldTimeout != null)
143             oldTimeout.cancel();
144     }
145 
146     protected long checkIdleTimeout()
147     {
148         if (isOpen())
149         {
150             long idleTimestamp = getIdleTimestamp();
151             long idleTimeout = getIdleTimeout();
152             long idleElapsed = System.currentTimeMillis() - idleTimestamp;
153             long idleLeft = idleTimeout - idleElapsed;
154 
155             if (LOG.isDebugEnabled())
156                 LOG.debug("{} idle timeout check, elapsed: {} ms, remaining: {} ms", this, idleElapsed, idleLeft);
157 
158             if (idleTimestamp != 0 && idleTimeout > 0)
159             {
160                 if (idleLeft <= 0)
161                 {
162                     if (LOG.isDebugEnabled())
163                         LOG.debug("{} idle timeout expired", this);
164                     try
165                     {
166                         onIdleExpired(new TimeoutException("Idle timeout expired: " + idleElapsed + "/" + idleTimeout + " ms"));
167                     }
168                     finally
169                     {
170                         notIdle();
171                     }
172                 }
173             }
174 
175             return idleLeft >= 0 ? idleLeft : 0;
176         }
177         return -1;
178     }
179 
180     /**
181      * This abstract method is called when the idle timeout has expired.
182      *
183      * @param timeout a TimeoutException
184      */
185     protected abstract void onIdleExpired(TimeoutException timeout);
186 
187     /**
188      * This abstract method should be called to check if idle timeouts
189      * should still be checked.
190      *
191      * @return True if the entity monitored should still be checked for idle timeouts
192      */
193     public abstract boolean isOpen();
194 }