1 // ========================================================================
2 // Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
3 // ------------------------------------------------------------------------
4 // All rights reserved. This program and the accompanying materials
5 // are made available under the terms of the Eclipse Public License v1.0
6 // and Apache License v2.0 which accompanies this distribution.
7 // The Eclipse Public License is available at
8 // http://www.eclipse.org/legal/epl-v10.html
9 // The Apache License v2.0 is available at
10 // http://www.opensource.org/licenses/apache2.0.php
11 // You may elect to redistribute this code under either of these licenses.
12 // ========================================================================
13
14 package org.eclipse.jetty.util.thread;
15
16 import org.eclipse.jetty.util.log.Log;
17
18
19 /* ------------------------------------------------------------ */
20 /** Timeout queue.
21 * This class implements a timeout queue for timers that are at least as likely to be cancelled as they are to expire.
22 * Unlike the util timeout class, the duration of the timeouts is shared by all scheduled tasks and if the duration
23 * is changed, this affects all scheduled tasks.
24 * <p>
25 * The nested class Task should be extended by users of this class to obtain call back notification of
26 * expiries.
27 *
28 *
29 *
30 */
31 public class Timeout
32 {
33 private Object _lock;
34 private long _duration;
35 private long _now=System.currentTimeMillis();
36 private Task _head=new Task();
37
38 /* ------------------------------------------------------------ */
39 public Timeout()
40 {
41 _lock=new Object();
42 _head._timeout=this;
43 }
44
45 /* ------------------------------------------------------------ */
46 public Timeout(Object lock)
47 {
48 _lock=lock;
49 _head._timeout=this;
50 }
51
52 /* ------------------------------------------------------------ */
53 /**
54 * @return Returns the duration.
55 */
56 public long getDuration()
57 {
58 return _duration;
59 }
60
61 /* ------------------------------------------------------------ */
62 /**
63 * @param duration The duration to set.
64 */
65 public void setDuration(long duration)
66 {
67 _duration = duration;
68 }
69
70 /* ------------------------------------------------------------ */
71 public long setNow()
72 {
73 synchronized (_lock)
74 {
75 _now=System.currentTimeMillis();
76 return _now;
77 }
78 }
79
80 /* ------------------------------------------------------------ */
81 public long getNow()
82 {
83 synchronized (_lock)
84 {
85 return _now;
86 }
87 }
88
89 /* ------------------------------------------------------------ */
90 public void setNow(long now)
91 {
92 synchronized (_lock)
93 {
94 _now=now;
95 }
96 }
97
98 /* ------------------------------------------------------------ */
99 /** Get an expired tasks.
100 * This is called instead of {@link #tick()} to obtain the next
101 * expired Task, but without calling it's {@link Task#expire()} or
102 * {@link Task#expired()} methods.
103 *
104 * @returns the next expired task or null.
105 */
106 public Task expired()
107 {
108 synchronized (_lock)
109 {
110 long _expiry = _now-_duration;
111
112 if (_head._next!=_head)
113 {
114 Task task = _head._next;
115 if (task._timestamp>_expiry)
116 return null;
117
118 task.unlink();
119 task._expired=true;
120 return task;
121 }
122 return null;
123 }
124 }
125
126 /* ------------------------------------------------------------ */
127 public void tick(long now)
128 {
129 long _expiry = -1;
130
131 Task task=null;
132 while (true)
133 {
134 try
135 {
136 synchronized (_lock)
137 {
138 if (_expiry==-1)
139 {
140 if (now!=-1)
141 _now=now;
142 _expiry = _now-_duration;
143 }
144
145 task= _head._next;
146 if (task==_head || task._timestamp>_expiry)
147 break;
148 task.unlink();
149 task._expired=true;
150 task.expire();
151 }
152
153 task.expired();
154 }
155 catch(Throwable th)
156 {
157 Log.warn(Log.EXCEPTION,th);
158 }
159 }
160 }
161
162 /* ------------------------------------------------------------ */
163 public void tick()
164 {
165 tick(-1);
166 }
167
168 /* ------------------------------------------------------------ */
169 public void schedule(Task task)
170 {
171 schedule(task,0L);
172 }
173
174 /* ------------------------------------------------------------ */
175 /**
176 * @param task
177 * @param delay A delay in addition to the default duration of the timeout
178 */
179 public void schedule(Task task,long delay)
180 {
181 synchronized (_lock)
182 {
183 if (task._timestamp!=0)
184 {
185 task.unlink();
186 task._timestamp=0;
187 }
188 task._timeout=this;
189 task._expired=false;
190 task._delay=delay;
191 task._timestamp = _now+delay;
192
193 Task last=_head._prev;
194 while (last!=_head)
195 {
196 if (last._timestamp <= task._timestamp)
197 break;
198 last=last._prev;
199 }
200 last.link(task);
201 }
202 }
203
204
205 /* ------------------------------------------------------------ */
206 public void cancelAll()
207 {
208 synchronized (_lock)
209 {
210 _head._next=_head._prev=_head;
211 }
212 }
213
214 /* ------------------------------------------------------------ */
215 public boolean isEmpty()
216 {
217 synchronized (_lock)
218 {
219 return _head._next==_head;
220 }
221 }
222
223 /* ------------------------------------------------------------ */
224 public long getTimeToNext()
225 {
226 synchronized (_lock)
227 {
228 if (_head._next==_head)
229 return -1;
230 long to_next = _duration+_head._next._timestamp-_now;
231 return to_next<0?0:to_next;
232 }
233 }
234
235 /* ------------------------------------------------------------ */
236 public String toString()
237 {
238 StringBuffer buf = new StringBuffer();
239 buf.append(super.toString());
240
241 Task task = _head._next;
242 while (task!=_head)
243 {
244 buf.append("-->");
245 buf.append(task);
246 task=task._next;
247 }
248
249 return buf.toString();
250 }
251
252 /* ------------------------------------------------------------ */
253 /* ------------------------------------------------------------ */
254 /* ------------------------------------------------------------ */
255 /* ------------------------------------------------------------ */
256 /** Task.
257 * The base class for scheduled timeouts. This class should be
258 * extended to implement the expire() method, which is called if the
259 * timeout expires.
260 *
261 *
262 *
263 */
264 public static class Task
265 {
266 Task _next;
267 Task _prev;
268 Timeout _timeout;
269 long _delay;
270 long _timestamp=0;
271 boolean _expired=false;
272
273 /* ------------------------------------------------------------ */
274 protected Task()
275 {
276 _next=_prev=this;
277 }
278
279 /* ------------------------------------------------------------ */
280 public long getTimestamp()
281 {
282 return _timestamp;
283 }
284
285 /* ------------------------------------------------------------ */
286 public long getAge()
287 {
288 Timeout t = _timeout;
289 if (t!=null && t._now!=0 && _timestamp!=0)
290 return t._now-_timestamp;
291 return 0;
292 }
293
294 /* ------------------------------------------------------------ */
295 private void unlink()
296 {
297 _next._prev=_prev;
298 _prev._next=_next;
299 _next=_prev=this;
300 _expired=false;
301 }
302
303 /* ------------------------------------------------------------ */
304 private void link(Task task)
305 {
306 Task next_next = _next;
307 _next._prev=task;
308 _next=task;
309 _next._next=next_next;
310 _next._prev=this;
311 }
312
313 /* ------------------------------------------------------------ */
314 /** Schedule the task on the given timeout.
315 * The task exiry will be called after the timeout duration.
316 * @param timer
317 */
318 public void schedule(Timeout timer)
319 {
320 timer.schedule(this);
321 }
322
323 /* ------------------------------------------------------------ */
324 /** Schedule the task on the given timeout.
325 * The task exiry will be called after the timeout duration.
326 * @param timer
327 */
328 public void schedule(Timeout timer, long delay)
329 {
330 timer.schedule(this,delay);
331 }
332
333 /* ------------------------------------------------------------ */
334 /** Reschedule the task on the current timeout.
335 * The task timeout is rescheduled as if it had been cancelled and
336 * scheduled on the current timeout.
337 */
338 public void reschedule()
339 {
340 Timeout timeout = _timeout;
341 if (timeout!=null)
342 timeout.schedule(this,_delay);
343 }
344
345 /* ------------------------------------------------------------ */
346 /** Cancel the task.
347 * Remove the task from the timeout.
348 */
349 public void cancel()
350 {
351 Timeout timeout = _timeout;
352 if (timeout!=null)
353 {
354 synchronized (timeout._lock)
355 {
356 unlink();
357 _timestamp=0;
358 }
359 }
360 }
361
362 /* ------------------------------------------------------------ */
363 public boolean isExpired() { return _expired; }
364
365 /* ------------------------------------------------------------ */
366 public boolean isScheduled() { return _next!=this; }
367
368 /* ------------------------------------------------------------ */
369 /** Expire task.
370 * This method is called when the timeout expires. It is called
371 * in the scope of the synchronize block (on this) that sets
372 * the {@link #isExpired()} state to true.
373 * @see #expired() For an unsynchronized callback.
374 */
375 protected void expire(){}
376
377 /* ------------------------------------------------------------ */
378 /** Expire task.
379 * This method is called when the timeout expires. It is called
380 * outside of any synchronization scope and may be delayed.
381 *
382 */
383 protected void expired(){}
384
385 }
386
387 }