1//
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}
381