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 volatile 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 return _now=System.currentTimeMillis();
74 }
75
76 /* ------------------------------------------------------------ */
77 public long getNow()
78 {
79 return _now;
80 }
81
82 /* ------------------------------------------------------------ */
83 public void setNow(long now)
84 {
85 _now=now;
86 }
87
88 /* ------------------------------------------------------------ */
89 /** Get an expired tasks.
90 * This is called instead of {@link #tick()} to obtain the next
91 * expired Task, but without calling it's {@link Task#expire()} or
92 * {@link Task#expired()} methods.
93 *
94 * @returns the next expired task or null.
95 */
96 public Task expired()
97 {
98 synchronized (_lock)
99 {
100 long _expiry = _now-_duration;
101
102 if (_head._next!=_head)
103 {
104 Task task = _head._next;
105 if (task._timestamp>_expiry)
106 return null;
107
108 task.unlink();
109 task._expired=true;
110 return task;
111 }
112 return null;
113 }
114 }
115
116 /* ------------------------------------------------------------ */
117 public void tick()
118 {
119 final long expiry = _now-_duration;
120
121 Task task=null;
122 while (true)
123 {
124 try
125 {
126 synchronized (_lock)
127 {
128 task= _head._next;
129 if (task==_head || task._timestamp>expiry)
130 break;
131 task.unlink();
132 task._expired=true;
133 task.expire();
134 }
135
136 task.expired();
137 }
138 catch(Throwable th)
139 {
140 Log.warn(Log.EXCEPTION,th);
141 }
142 }
143 }
144
145 /* ------------------------------------------------------------ */
146 public void tick(long now)
147 {
148 _now=now;
149 tick();
150 }
151
152 /* ------------------------------------------------------------ */
153 public void schedule(Task task)
154 {
155 schedule(task,0L);
156 }
157
158 /* ------------------------------------------------------------ */
159 /**
160 * @param task
161 * @param delay A delay in addition to the default duration of the timeout
162 */
163 public void schedule(Task task,long delay)
164 {
165 synchronized (_lock)
166 {
167 if (task._timestamp!=0)
168 {
169 task.unlink();
170 task._timestamp=0;
171 }
172 task._timeout=this;
173 task._expired=false;
174 task._delay=delay;
175 task._timestamp = _now+delay;
176
177 Task last=_head._prev;
178 while (last!=_head)
179 {
180 if (last._timestamp <= task._timestamp)
181 break;
182 last=last._prev;
183 }
184 last.link(task);
185 }
186 }
187
188
189 /* ------------------------------------------------------------ */
190 public void cancelAll()
191 {
192 synchronized (_lock)
193 {
194 _head._next=_head._prev=_head;
195 }
196 }
197
198 /* ------------------------------------------------------------ */
199 public boolean isEmpty()
200 {
201 synchronized (_lock)
202 {
203 return _head._next==_head;
204 }
205 }
206
207 /* ------------------------------------------------------------ */
208 public long getTimeToNext()
209 {
210 synchronized (_lock)
211 {
212 if (_head._next==_head)
213 return -1;
214 long to_next = _duration+_head._next._timestamp-_now;
215 return to_next<0?0:to_next;
216 }
217 }
218
219 /* ------------------------------------------------------------ */
220 @Override
221 public String toString()
222 {
223 StringBuffer buf = new StringBuffer();
224 buf.append(super.toString());
225
226 Task task = _head._next;
227 while (task!=_head)
228 {
229 buf.append("-->");
230 buf.append(task);
231 task=task._next;
232 }
233
234 return buf.toString();
235 }
236
237 /* ------------------------------------------------------------ */
238 /* ------------------------------------------------------------ */
239 /* ------------------------------------------------------------ */
240 /* ------------------------------------------------------------ */
241 /** Task.
242 * The base class for scheduled timeouts. This class should be
243 * extended to implement the expire() method, which is called if the
244 * timeout expires.
245 *
246 *
247 *
248 */
249 public static class Task
250 {
251 Task _next;
252 Task _prev;
253 Timeout _timeout;
254 long _delay;
255 long _timestamp=0;
256 boolean _expired=false;
257
258 /* ------------------------------------------------------------ */
259 protected Task()
260 {
261 _next=_prev=this;
262 }
263
264 /* ------------------------------------------------------------ */
265 public long getTimestamp()
266 {
267 return _timestamp;
268 }
269
270 /* ------------------------------------------------------------ */
271 public long getAge()
272 {
273 final Timeout t = _timeout;
274 if (t!=null)
275 {
276 final long now=t._now;
277 if (now!=0 && _timestamp!=0)
278 return now-_timestamp;
279 }
280 return 0;
281 }
282
283 /* ------------------------------------------------------------ */
284 private void unlink()
285 {
286 _next._prev=_prev;
287 _prev._next=_next;
288 _next=_prev=this;
289 _expired=false;
290 }
291
292 /* ------------------------------------------------------------ */
293 private void link(Task task)
294 {
295 Task next_next = _next;
296 _next._prev=task;
297 _next=task;
298 _next._next=next_next;
299 _next._prev=this;
300 }
301
302 /* ------------------------------------------------------------ */
303 /** Schedule the task on the given timeout.
304 * The task exiry will be called after the timeout duration.
305 * @param timer
306 */
307 public void schedule(Timeout timer)
308 {
309 timer.schedule(this);
310 }
311
312 /* ------------------------------------------------------------ */
313 /** Schedule the task on the given timeout.
314 * The task exiry will be called after the timeout duration.
315 * @param timer
316 */
317 public void schedule(Timeout timer, long delay)
318 {
319 timer.schedule(this,delay);
320 }
321
322 /* ------------------------------------------------------------ */
323 /** Reschedule the task on the current timeout.
324 * The task timeout is rescheduled as if it had been cancelled and
325 * scheduled on the current timeout.
326 */
327 public void reschedule()
328 {
329 Timeout timeout = _timeout;
330 if (timeout!=null)
331 timeout.schedule(this,_delay);
332 }
333
334 /* ------------------------------------------------------------ */
335 /** Cancel the task.
336 * Remove the task from the timeout.
337 */
338 public void cancel()
339 {
340 Timeout timeout = _timeout;
341 if (timeout!=null)
342 {
343 synchronized (timeout._lock)
344 {
345 unlink();
346 _timestamp=0;
347 }
348 }
349 }
350
351 /* ------------------------------------------------------------ */
352 public boolean isExpired() { return _expired; }
353
354 /* ------------------------------------------------------------ */
355 public boolean isScheduled() { return _next!=this; }
356
357 /* ------------------------------------------------------------ */
358 /** Expire task.
359 * This method is called when the timeout expires. It is called
360 * in the scope of the synchronize block (on this) that sets
361 * the {@link #isExpired()} state to true.
362 * @see #expired() For an unsynchronized callback.
363 */
364 protected void expire(){}
365
366 /* ------------------------------------------------------------ */
367 /** Expire task.
368 * This method is called when the timeout expires. It is called
369 * outside of any synchronization scope and may be delayed.
370 *
371 */
372 public void expired(){}
373
374 }
375
376 }