View Javadoc

1   // ========================================================================
2   // Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // ========================================================================
13  
14  package org.eclipse.jetty.util.thread;
15  
16  import org.eclipse.jetty.util.log.Log;
17  import org.eclipse.jetty.util.log.Logger;
18  
19  
20  /* ------------------------------------------------------------ */
21  /** Timeout queue.
22   * This class implements a timeout queue for timers that are at least as likely to be cancelled as they are to expire.
23   * Unlike the util timeout class, the duration of the timeouts is shared by all scheduled tasks and if the duration 
24   * is changed, this affects all scheduled tasks.
25   * <p>
26   * The nested class Task should be extended by users of this class to obtain call back notification of 
27   * expires. 
28   */
29  public class Timeout
30  {
31      private static final Logger LOG = Log.getLogger(Timeout.class);
32      private Object _lock;
33      private long _duration;
34      private volatile long _now=System.currentTimeMillis();
35      private Task _head=new Task();
36  
37      /* ------------------------------------------------------------ */
38      public Timeout()
39      {
40          _lock=new Object();
41          _head._timeout=this;
42      }
43  
44      /* ------------------------------------------------------------ */
45      public Timeout(Object lock)
46      {
47          _lock=lock;
48          _head._timeout=this;
49      }
50  
51      /* ------------------------------------------------------------ */
52      /**
53       * @return Returns the duration.
54       */
55      public long getDuration()
56      {
57          return _duration;
58      }
59  
60      /* ------------------------------------------------------------ */
61      /**
62       * @param duration The duration to set.
63       */
64      public void setDuration(long duration)
65      {
66          _duration = duration;
67      }
68  
69      /* ------------------------------------------------------------ */
70      public long setNow()
71      {
72          return _now=System.currentTimeMillis();
73      }
74      
75      /* ------------------------------------------------------------ */
76      public long getNow()
77      {
78          return _now;
79      }
80  
81      /* ------------------------------------------------------------ */
82      public void setNow(long now)
83      {
84          _now=now;
85      }
86  
87      /* ------------------------------------------------------------ */
88      /** Get an expired tasks.
89       * This is called instead of {@link #tick()} to obtain the next
90       * expired Task, but without calling it's {@link Task#expire()} or
91       * {@link Task#expired()} methods.
92       * 
93       * @return the next expired task or null.
94       */
95      public Task expired()
96      {
97          synchronized (_lock)
98          {
99              long _expiry = _now-_duration;
100 
101             if (_head._next!=_head)
102             {
103                 Task task = _head._next;
104                 if (task._timestamp>_expiry)
105                     return null;
106 
107                 task.unlink();
108                 task._expired=true;
109                 return task;
110             }
111             return null;
112         }
113     }
114 
115     /* ------------------------------------------------------------ */
116     public void tick()
117     {
118         final long expiry = _now-_duration;
119 
120         Task task=null;
121         while (true)
122         {
123             try
124             {
125                 synchronized (_lock)
126                 {
127                     task= _head._next;
128                     if (task==_head || task._timestamp>expiry)
129                         break;
130                     task.unlink();
131                     task._expired=true;
132                     task.expire();
133                 }
134                 
135                 task.expired();
136             }
137             catch(Throwable th)
138             {
139                 LOG.warn(Log.EXCEPTION,th);
140             }
141         }
142     }
143 
144     /* ------------------------------------------------------------ */
145     public void tick(long now)
146     {
147         _now=now;
148         tick();
149     }
150 
151     /* ------------------------------------------------------------ */
152     public void schedule(Task task)
153     {
154         schedule(task,0L);
155     }
156     
157     /* ------------------------------------------------------------ */
158     /**
159      * @param task
160      * @param delay A delay in addition to the default duration of the timeout
161      */
162     public void schedule(Task task,long delay)
163     {
164         synchronized (_lock)
165         {
166             if (task._timestamp!=0)
167             {
168                 task.unlink();
169                 task._timestamp=0;
170             }
171             task._timeout=this;
172             task._expired=false;
173             task._delay=delay;
174             task._timestamp = _now+delay;
175 
176             Task last=_head._prev;
177             while (last!=_head)
178             {
179                 if (last._timestamp <= task._timestamp)
180                     break;
181                 last=last._prev;
182             }
183             last.link(task);
184         }
185     }
186 
187 
188     /* ------------------------------------------------------------ */
189     public void cancelAll()
190     {
191         synchronized (_lock)
192         {
193             _head._next=_head._prev=_head;
194         }
195     }
196 
197     /* ------------------------------------------------------------ */
198     public boolean isEmpty()
199     {
200         synchronized (_lock)
201         {
202             return _head._next==_head;
203         }
204     }
205 
206     /* ------------------------------------------------------------ */
207     public long getTimeToNext()
208     {
209         synchronized (_lock)
210         {
211             if (_head._next==_head)
212                 return -1;
213             long to_next = _duration+_head._next._timestamp-_now;
214             return to_next<0?0:to_next;
215         }
216     }
217 
218     /* ------------------------------------------------------------ */
219     @Override
220     public String toString()
221     {
222         StringBuffer buf = new StringBuffer();
223         buf.append(super.toString());
224         
225         Task task = _head._next;
226         while (task!=_head)
227         {
228             buf.append("-->");
229             buf.append(task);
230             task=task._next;
231         }
232         
233         return buf.toString();
234     }
235 
236     /* ------------------------------------------------------------ */
237     /* ------------------------------------------------------------ */
238     /* ------------------------------------------------------------ */
239     /* ------------------------------------------------------------ */
240     /** Task.
241      * The base class for scheduled timeouts.  This class should be
242      * extended to implement the expire() method, which is called if the
243      * timeout expires.
244      * 
245      * 
246      *
247      */
248     public static class Task
249     {
250         Task _next;
251         Task _prev;
252         Timeout _timeout;
253         long _delay;
254         long _timestamp=0;
255         boolean _expired=false;
256 
257         /* ------------------------------------------------------------ */
258         protected Task()
259         {
260             _next=_prev=this;
261         }
262 
263         /* ------------------------------------------------------------ */
264         public long getTimestamp()
265         {
266             return _timestamp;
267         }
268 
269         /* ------------------------------------------------------------ */
270         public long getAge()
271         {
272             final Timeout t = _timeout;
273             if (t!=null)
274             {
275                 final long now=t._now;
276                 if (now!=0 && _timestamp!=0)
277                     return now-_timestamp;
278             }
279             return 0;
280         }
281 
282         /* ------------------------------------------------------------ */
283         private void unlink()
284         {
285             _next._prev=_prev;
286             _prev._next=_next;
287             _next=_prev=this;
288             _expired=false;
289         }
290 
291         /* ------------------------------------------------------------ */
292         private void link(Task task)
293         {
294             Task next_next = _next;
295             _next._prev=task;
296             _next=task;
297             _next._next=next_next;
298             _next._prev=this;   
299         }
300         
301         /* ------------------------------------------------------------ */
302         /** Schedule the task on the given timeout.
303          * The task exiry will be called after the timeout duration.
304          * @param timer
305          */
306         public void schedule(Timeout timer)
307         {
308             timer.schedule(this);
309         }
310         
311         /* ------------------------------------------------------------ */
312         /** Schedule the task on the given timeout.
313          * The task exiry will be called after the timeout duration.
314          * @param timer
315          */
316         public void schedule(Timeout timer, long delay)
317         {
318             timer.schedule(this,delay);
319         }
320         
321         /* ------------------------------------------------------------ */
322         /** Reschedule the task on the current timeout.
323          * The task timeout is rescheduled as if it had been cancelled and
324          * scheduled on the current timeout.
325          */
326         public void reschedule()
327         {
328             Timeout timeout = _timeout;
329             if (timeout!=null)
330                 timeout.schedule(this,_delay);
331         }
332         
333         /* ------------------------------------------------------------ */
334         /** Cancel the task.
335          * Remove the task from the timeout.
336          */
337         public void cancel()
338         {
339             Timeout timeout = _timeout;
340             if (timeout!=null)
341             {
342                 synchronized (timeout._lock)
343                 {
344                     unlink();
345                     _timestamp=0;
346                 }
347             }
348         }
349         
350         /* ------------------------------------------------------------ */
351         public boolean isExpired() { return _expired; }
352 
353         /* ------------------------------------------------------------ */
354 	public boolean isScheduled() { return _next!=this; }
355         
356         /* ------------------------------------------------------------ */
357         /** Expire task.
358          * This method is called when the timeout expires. It is called
359          * in the scope of the synchronize block (on this) that sets 
360          * the {@link #isExpired()} state to true.
361          * @see #expired() For an unsynchronized callback.
362          */
363         protected void expire(){}
364 
365         /* ------------------------------------------------------------ */
366         /** Expire task.
367          * This method is called when the timeout expires. It is called 
368          * outside of any synchronization scope and may be delayed. 
369          * 
370          */
371         public void expired(){}
372 
373     }
374 
375 }