View Javadoc

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