View Javadoc

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