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