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 }