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 timouts 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   * <p>
30   * This class is synchronized, but the callback to expired is not called within the synchronized scope.
31   * 
32   * @author gregw
33   *
34   */
35  public class Timeout
36  {
37      private final Object _mutex;
38      private long _duration;
39      private long _now=System.currentTimeMillis();
40      private Task _head=new Task();
41  
42      public Timeout()
43      {
44          _mutex=this;
45          _head._timeout=this;
46      }
47      
48      public Timeout(final Object mutex)
49      {
50          _mutex=mutex==null?this:mutex;
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 void setNow()
74      {
75          _now=System.currentTimeMillis();
76      }
77      
78      /* ------------------------------------------------------------ */
79      public long getNow()
80      {
81          return _now;
82      }
83  
84      /* ------------------------------------------------------------ */
85      public void setNow(long now)
86      {
87          _now=now;
88      }
89  
90      /* ------------------------------------------------------------ */
91      public void tick()
92      {
93          long expiry = _now-_duration;
94          
95          while (true)
96          {
97              Task expired=null;
98              synchronized (_mutex)
99              {
100                 if (_head._next!=_head && _head._next._timestamp<=expiry)
101                 {
102                     expired=_head._next;
103                     expired.unlink();
104                     expired._expired=true;
105                 }
106             }
107             if (expired!=null)
108             {
109                 try
110                 {
111                     expired.expired();
112                 }
113                 catch(Throwable th)
114                 {
115                     Log.warn(Log.EXCEPTION,th);
116                 }
117             }
118             else
119                 break;
120         }
121     }
122 
123     /* ------------------------------------------------------------ */
124     public void schedule(Task task)
125     {
126         schedule(task,0L);
127     }
128     
129     /* ------------------------------------------------------------ */
130     public void schedule(Task task,long delay)
131     {
132         if (task._timeout!=null && task._timeout!=this)
133         {
134             task.cancel();
135         }
136         
137         synchronized (_mutex)
138         {
139             if (task._timestamp!=0)
140             {
141                 task.unlink();
142                 task._timestamp=0;
143             }
144             task._expired=false;
145             task._delay=delay;
146             task._timestamp = _now+delay;
147 
148             Task last=_head._prev;
149             while (last!=_head)
150             {
151                 if (last._timestamp <= task._timestamp)
152                     break;
153                 last=last._prev;
154             }
155             last.setNext(task);
156         }
157     }
158 
159 
160     /* ------------------------------------------------------------ */
161     public void cancelAll()
162     {
163         synchronized (_mutex)
164         {
165             _head._next=_head._prev=_head;
166             // TODO call a cancel callback?
167         }
168     }
169 
170     /* ------------------------------------------------------------ */
171     public boolean isEmpty()
172     {
173         synchronized (_mutex)
174         {
175             return _head._next==_head;
176         }
177     }
178 
179     /* ------------------------------------------------------------ */
180     public long getTimeToNext()
181     {
182         synchronized (_mutex)
183         {
184             if (_head._next==_head)
185                 return -1;
186             long to_next = _duration+_head._next._timestamp-_now;
187             return to_next<0?0:to_next;
188         }
189     }
190 
191     /* ------------------------------------------------------------ */
192     public String toString()
193     {
194         StringBuilder buf = new StringBuilder();
195         buf.append(super.toString());
196 
197         synchronized (_mutex)
198         {
199             Task task = _head._next;
200             while (task!=_head)
201             {
202                 buf.append("-->");
203                 buf.append(task);
204                 task=task._next;
205             }
206         }
207         
208         return buf.toString();
209     }
210 
211     /* ------------------------------------------------------------ */
212     /* ------------------------------------------------------------ */
213     /* ------------------------------------------------------------ */
214     /* ------------------------------------------------------------ */
215     /** Task.
216      * The base class for scheduled timeouts.  This class should be
217      * extended to implement the {@link #expire()} or {@link #expired()} method, which is called if the
218      * timeout expires.
219      * 
220      * @author gregw
221      *
222      */
223     public static class Task
224     {
225         Task _next;
226         Task _prev;
227         Timeout _timeout;
228         long _delay;
229         long _timestamp=0;
230         boolean _expired=false;
231 
232         /* ------------------------------------------------------------ */
233         public Task()
234         {
235             _next=_prev=this;
236         }
237 
238         /* ------------------------------------------------------------ */
239         public long getTimestamp()
240         {
241             return _timestamp;
242         }
243 
244         /* ------------------------------------------------------------ */
245         public long getAge()
246         {
247             Timeout t = _timeout;
248             if (t!=null && t._now!=0 && _timestamp!=0)
249                 return t._now-_timestamp;
250             return 0;
251         }
252 
253         /* ------------------------------------------------------------ */
254         private void unlink()
255         {
256             _next._prev=_prev;
257             _prev._next=_next;
258             _next=_prev=this;
259             _timeout=null;
260             _expired=false;
261         }
262 
263         /* ------------------------------------------------------------ */
264         private void setNext(Task task)
265         {
266             if (_timeout==null || 
267                 task._timeout!=null && task._timeout!=_timeout ||    
268                 task._next!=task)
269                 throw new IllegalStateException();
270             Task next_next = _next;
271             _next._prev=task;
272             _next=task;
273             _next._next=next_next;
274             _next._prev=this;   
275             _next._timeout=_timeout;
276         }
277         
278         /* ------------------------------------------------------------ */
279         /** Cancel the task.
280          * Remove the task from the timeout.
281          */
282         public void cancel()
283         {
284             if (_timeout!=null)
285             {
286                 synchronized (_timeout._mutex)
287                 {
288                     _timestamp=0;
289                     unlink();
290                 }
291             }
292         }
293         
294         /* ------------------------------------------------------------ */
295         public boolean isExpired() { return _expired; }
296 
297         /* ------------------------------------------------------------ */
298         public boolean isScheduled() { return _next!=this; }
299         
300         /* ------------------------------------------------------------ */
301         /** Expire task.
302          * This method is called when the timeout expires. It is called 
303          * outside of any synchronization scope and may be delayed. 
304          * 
305          * @see #expire() For a synchronized callback.
306          */
307         public void expired(){}
308 
309     }
310 
311 }