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