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