blob: 6fabbddc27486c75c50166b89cafee14bfab68ef [file] [log] [blame]
Jake Slack03928ae2014-05-13 18:41:56 -07001//
2// ========================================================================
3// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4// ------------------------------------------------------------------------
5// All rights reserved. This program and the accompanying materials
6// are made available under the terms of the Eclipse Public License v1.0
7// and Apache License v2.0 which accompanies this distribution.
8//
9// The Eclipse Public License is available at
10// http://www.eclipse.org/legal/epl-v10.html
11//
12// The Apache License v2.0 is available at
13// http://www.opensource.org/licenses/apache2.0.php
14//
15// You may elect to redistribute this code under either of these licenses.
16// ========================================================================
17//
18
19package org.eclipse.jetty.util.thread;
20
21import org.eclipse.jetty.util.log.Log;
22import org.eclipse.jetty.util.log.Logger;
23
24
25/* ------------------------------------------------------------ */
26/** Timeout queue.
27 * This class implements a timeout queue for timers that are at least as likely to be cancelled as they are to expire.
28 * Unlike the util timeout class, the duration of the timeouts is shared by all scheduled tasks and if the duration
29 * is changed, this affects all scheduled tasks.
30 * <p>
31 * The nested class Task should be extended by users of this class to obtain call back notification of
32 * expires.
33 */
34public class Timeout
35{
36 private static final Logger LOG = Log.getLogger(Timeout.class);
37 private Object _lock;
38 private long _duration;
39 private volatile long _now=System.currentTimeMillis();
40 private Task _head=new Task();
41
42 /* ------------------------------------------------------------ */
43 public Timeout()
44 {
45 _lock=new Object();
46 _head._timeout=this;
47 }
48
49 /* ------------------------------------------------------------ */
50 public Timeout(Object lock)
51 {
52 _lock=lock;
53 _head._timeout=this;
54 }
55
56 /* ------------------------------------------------------------ */
57 /**
58 * @return Returns the duration.
59 */
60 public long getDuration()
61 {
62 return _duration;
63 }
64
65 /* ------------------------------------------------------------ */
66 /**
67 * @param duration The duration to set.
68 */
69 public void setDuration(long duration)
70 {
71 _duration = duration;
72 }
73
74 /* ------------------------------------------------------------ */
75 public long setNow()
76 {
77 return _now=System.currentTimeMillis();
78 }
79
80 /* ------------------------------------------------------------ */
81 public long getNow()
82 {
83 return _now;
84 }
85
86 /* ------------------------------------------------------------ */
87 public void setNow(long now)
88 {
89 _now=now;
90 }
91
92 /* ------------------------------------------------------------ */
93 /** Get an expired tasks.
94 * This is called instead of {@link #tick()} to obtain the next
95 * expired Task, but without calling it's {@link Task#expire()} or
96 * {@link Task#expired()} methods.
97 *
98 * @return the next expired task or null.
99 */
100 public Task expired()
101 {
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()
122 {
123 final long expiry = _now-_duration;
124
125 Task task=null;
126 while (true)
127 {
128 try
129 {
130 synchronized (_lock)
131 {
132 task= _head._next;
133 if (task==_head || task._timestamp>expiry)
134 break;
135 task.unlink();
136 task._expired=true;
137 task.expire();
138 }
139
140 task.expired();
141 }
142 catch(Throwable th)
143 {
144 LOG.warn(Log.EXCEPTION,th);
145 }
146 }
147 }
148
149 /* ------------------------------------------------------------ */
150 public void tick(long now)
151 {
152 _now=now;
153 tick();
154 }
155
156 /* ------------------------------------------------------------ */
157 public void schedule(Task task)
158 {
159 schedule(task,0L);
160 }
161
162 /* ------------------------------------------------------------ */
163 /**
164 * @param task
165 * @param delay A delay in addition to the default duration of the timeout
166 */
167 public void schedule(Task task,long delay)
168 {
169 synchronized (_lock)
170 {
171 if (task._timestamp!=0)
172 {
173 task.unlink();
174 task._timestamp=0;
175 }
176 task._timeout=this;
177 task._expired=false;
178 task._delay=delay;
179 task._timestamp = _now+delay;
180
181 Task last=_head._prev;
182 while (last!=_head)
183 {
184 if (last._timestamp <= task._timestamp)
185 break;
186 last=last._prev;
187 }
188 last.link(task);
189 }
190 }
191
192
193 /* ------------------------------------------------------------ */
194 public void cancelAll()
195 {
196 synchronized (_lock)
197 {
198 _head._next=_head._prev=_head;
199 }
200 }
201
202 /* ------------------------------------------------------------ */
203 public boolean isEmpty()
204 {
205 synchronized (_lock)
206 {
207 return _head._next==_head;
208 }
209 }
210
211 /* ------------------------------------------------------------ */
212 public long getTimeToNext()
213 {
214 synchronized (_lock)
215 {
216 if (_head._next==_head)
217 return -1;
218 long to_next = _duration+_head._next._timestamp-_now;
219 return to_next<0?0:to_next;
220 }
221 }
222
223 /* ------------------------------------------------------------ */
224 @Override
225 public String toString()
226 {
227 StringBuffer buf = new StringBuffer();
228 buf.append(super.toString());
229
230 Task task = _head._next;
231 while (task!=_head)
232 {
233 buf.append("-->");
234 buf.append(task);
235 task=task._next;
236 }
237
238 return buf.toString();
239 }
240
241 /* ------------------------------------------------------------ */
242 /* ------------------------------------------------------------ */
243 /* ------------------------------------------------------------ */
244 /* ------------------------------------------------------------ */
245 /** Task.
246 * The base class for scheduled timeouts. This class should be
247 * extended to implement the expire() method, which is called if the
248 * timeout expires.
249 *
250 *
251 *
252 */
253 public static class Task
254 {
255 Task _next;
256 Task _prev;
257 Timeout _timeout;
258 long _delay;
259 long _timestamp=0;
260 boolean _expired=false;
261
262 /* ------------------------------------------------------------ */
263 protected Task()
264 {
265 _next=_prev=this;
266 }
267
268 /* ------------------------------------------------------------ */
269 public long getTimestamp()
270 {
271 return _timestamp;
272 }
273
274 /* ------------------------------------------------------------ */
275 public long getAge()
276 {
277 final Timeout t = _timeout;
278 if (t!=null)
279 {
280 final long now=t._now;
281 if (now!=0 && _timestamp!=0)
282 return now-_timestamp;
283 }
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 protected 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}