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