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