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 }