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