1 //========================================================================
2 //$Id: Timeout.java,v 1.3 2005/11/11 22:55:41 gregwilkins Exp $
3 //Copyright 2004-2005 Mort Bay Consulting Pty. Ltd.
4 //------------------------------------------------------------------------
5 //Licensed under the Apache License, Version 2.0 (the "License");
6 //you may not use this file except in compliance with the License.
7 //You may obtain a copy of the License at
8 //http://www.apache.org/licenses/LICENSE-2.0
9 //Unless required by applicable law or agreed to in writing, software
10 //distributed under the License is distributed on an "AS IS" BASIS,
11 //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 //See the License for the specific language governing permissions and
13 //limitations under the License.
14 //========================================================================
15
16 package org.mortbay.thread;
17
18 import org.mortbay.log.Log;
19
20
21 /* ------------------------------------------------------------ */
22 /** Timeout queue.
23 * This class implements a timeout queue for timers that are at least as likely to be cancelled as they are to expire.
24 * Unlike the util timeout class, the duration of the timouts is shared by all scheduled tasks and if the duration
25 * is changed, this affects all scheduled tasks.
26 * <p>
27 * The nested class Task should be extended by users of this class to obtain call back notification of
28 * expiries.
29 * <p>
30 * This class is synchronized, but the callback to expired is not called within the synchronized scope.
31 *
32 * @author gregw
33 *
34 */
35 public class Timeout
36 {
37 private final Object _mutex;
38 private long _duration;
39 private long _now=System.currentTimeMillis();
40 private Task _head=new Task();
41
42 public Timeout()
43 {
44 _mutex=this;
45 _head._timeout=this;
46 }
47
48 public Timeout(final Object mutex)
49 {
50 _mutex=mutex==null?this:mutex;
51 _head._timeout=this;
52 }
53
54 /* ------------------------------------------------------------ */
55 /**
56 * @return Returns the duration.
57 */
58 public long getDuration()
59 {
60 return _duration;
61 }
62
63 /* ------------------------------------------------------------ */
64 /**
65 * @param duration The duration to set.
66 */
67 public void setDuration(long duration)
68 {
69 _duration = duration;
70 }
71
72 /* ------------------------------------------------------------ */
73 public void setNow()
74 {
75 _now=System.currentTimeMillis();
76 }
77
78 /* ------------------------------------------------------------ */
79 public long getNow()
80 {
81 return _now;
82 }
83
84 /* ------------------------------------------------------------ */
85 public void setNow(long now)
86 {
87 _now=now;
88 }
89
90 /* ------------------------------------------------------------ */
91 public void tick()
92 {
93 long expiry = _now-_duration;
94
95 while (true)
96 {
97 Task expired=null;
98 synchronized (_mutex)
99 {
100 if (_head._next!=_head && _head._next._timestamp<=expiry)
101 {
102 expired=_head._next;
103 expired.unlink();
104 expired._expired=true;
105 }
106 }
107 if (expired!=null)
108 {
109 try
110 {
111 expired.expired();
112 }
113 catch(Throwable th)
114 {
115 Log.warn(Log.EXCEPTION,th);
116 }
117 }
118 else
119 break;
120 }
121 }
122
123 /* ------------------------------------------------------------ */
124 public void schedule(Task task)
125 {
126 schedule(task,0L);
127 }
128
129 /* ------------------------------------------------------------ */
130 public void schedule(Task task,long delay)
131 {
132 if (task._timeout!=null && task._timeout!=this)
133 {
134 task.cancel();
135 }
136
137 synchronized (_mutex)
138 {
139 if (task._timestamp!=0)
140 {
141 task.unlink();
142 task._timestamp=0;
143 }
144 task._expired=false;
145 task._delay=delay;
146 task._timestamp = _now+delay;
147
148 Task last=_head._prev;
149 while (last!=_head)
150 {
151 if (last._timestamp <= task._timestamp)
152 break;
153 last=last._prev;
154 }
155 last.setNext(task);
156 }
157 }
158
159
160 /* ------------------------------------------------------------ */
161 public void cancelAll()
162 {
163 synchronized (_mutex)
164 {
165 _head._next=_head._prev=_head;
166 // TODO call a cancel callback?
167 }
168 }
169
170 /* ------------------------------------------------------------ */
171 public boolean isEmpty()
172 {
173 synchronized (_mutex)
174 {
175 return _head._next==_head;
176 }
177 }
178
179 /* ------------------------------------------------------------ */
180 public long getTimeToNext()
181 {
182 synchronized (_mutex)
183 {
184 if (_head._next==_head)
185 return -1;
186 long to_next = _duration+_head._next._timestamp-_now;
187 return to_next<0?0:to_next;
188 }
189 }
190
191 /* ------------------------------------------------------------ */
192 public String toString()
193 {
194 StringBuilder buf = new StringBuilder();
195 buf.append(super.toString());
196
197 synchronized (_mutex)
198 {
199 Task task = _head._next;
200 while (task!=_head)
201 {
202 buf.append("-->");
203 buf.append(task);
204 task=task._next;
205 }
206 }
207
208 return buf.toString();
209 }
210
211 /* ------------------------------------------------------------ */
212 /* ------------------------------------------------------------ */
213 /* ------------------------------------------------------------ */
214 /* ------------------------------------------------------------ */
215 /** Task.
216 * The base class for scheduled timeouts. This class should be
217 * extended to implement the {@link #expire()} or {@link #expired()} method, which is called if the
218 * timeout expires.
219 *
220 * @author gregw
221 *
222 */
223 public static class Task
224 {
225 Task _next;
226 Task _prev;
227 Timeout _timeout;
228 long _delay;
229 long _timestamp=0;
230 boolean _expired=false;
231
232 /* ------------------------------------------------------------ */
233 public Task()
234 {
235 _next=_prev=this;
236 }
237
238 /* ------------------------------------------------------------ */
239 public long getTimestamp()
240 {
241 return _timestamp;
242 }
243
244 /* ------------------------------------------------------------ */
245 public long getAge()
246 {
247 Timeout t = _timeout;
248 if (t!=null && t._now!=0 && _timestamp!=0)
249 return t._now-_timestamp;
250 return 0;
251 }
252
253 /* ------------------------------------------------------------ */
254 private void unlink()
255 {
256 _next._prev=_prev;
257 _prev._next=_next;
258 _next=_prev=this;
259 _timeout=null;
260 _expired=false;
261 }
262
263 /* ------------------------------------------------------------ */
264 private void setNext(Task task)
265 {
266 if (_timeout==null ||
267 task._timeout!=null && task._timeout!=_timeout ||
268 task._next!=task)
269 throw new IllegalStateException();
270 Task next_next = _next;
271 _next._prev=task;
272 _next=task;
273 _next._next=next_next;
274 _next._prev=this;
275 _next._timeout=_timeout;
276 }
277
278 /* ------------------------------------------------------------ */
279 /** Cancel the task.
280 * Remove the task from the timeout.
281 */
282 public void cancel()
283 {
284 if (_timeout!=null)
285 {
286 synchronized (_timeout._mutex)
287 {
288 _timestamp=0;
289 unlink();
290 }
291 }
292 }
293
294 /* ------------------------------------------------------------ */
295 public boolean isExpired() { return _expired; }
296
297 /* ------------------------------------------------------------ */
298 public boolean isScheduled() { return _next!=this; }
299
300 /* ------------------------------------------------------------ */
301 /** Expire task.
302 * This method is called when the timeout expires. It is called
303 * outside of any synchronization scope and may be delayed.
304 *
305 * @see #expire() For a synchronized callback.
306 */
307 public void expired(){}
308
309 }
310
311 }