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