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 }