summaryrefslogtreecommitdiff
path: root/java/src/TimerQueue.java
blob: 74bac5ec398fa45362126054dd93254601910571 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
/*************************************************
 *
 * = PACKAGE
 *    JACE.Reactor
 *
 * = FILENAME
 *    TimerQueue.java
 *
 *@author Prashant Jain
 *
 *************************************************/
package JACE.Reactor;

import java.util.*;
import JACE.ASX.*;

class TimerNode
{
  public TimerNode (EventHandler handler,
		    Object arg,
		    TimeValue timerValue,
		    TimeValue interval,
		    TimerNode next,
		    int timerId)
  {
    this.handler_ = handler;
    this.arg_ = arg;
    this.timerValue_ = timerValue;
    this.interval_ = interval;
    this.next_ = next;
    this.timerId_ = timerId;
  }

  public EventHandler handler_;
  // Handler to invoke <handleTimeout> on when a timeout occurs.

  public Object arg_;
  // Argument to pass to <handleTimeout>.

  public TimeValue timerValue_;
  // Time until the timer expires.

  public TimeValue interval_;
  // If this is a periodic timer this holds the time until the next
  // timeout.

  public TimerNode next_;
  // Pointer to next timer.
  
  public int timerId_;
  // Id of this timer (used to cancel timers before they expire).
}

class WaitObject extends TimedWait
{
  public boolean condition ()
  {
    return this.condition_;
  }
  
  public void condition (boolean c)
  {
    this.condition_ = c;
  }

  private boolean condition_ = false;
}

/**
 * <hr>
 * <h2>SYNOPSIS</h2>
 *<blockquote>
 *     Class that provides an interface to timers. 
 *
 *</blockquote>
 *
 * <h2>DESCRIPTION</h2>
 *<blockquote>
 *     This is a simple implementation that keeps a linked list of
 *     absolute timers. It allows multiple timers to be scheduled
 *     and returns a timer id for each timer scheduled. In addition,
 *     it allows periodic timers to be scheduled.
 *</blockquote>
 */
public class TimerQueue implements Runnable
{
  /**
   * Constructor.
   *@param createInternalThread flag specifying whether to create an
   * internal thread that runs the event loop. If it is true, a thread
   * is spawned and it runs the event loop, handling all timeout
   * events. If it is false, the caller is then responsible for calling
   * handleEvents () to run the event loop.
   */
  public TimerQueue (boolean createInternalThread)
  {
    this.eventLoopRunning_ = false;
    if (createInternalThread)
      new Thread (this).start ();
  }
  
  /**
   * The thread run method. Do *NOT* call this method! It gets called
   * automatically. 
   */
  public void run ()
  {
    this.handleEvents ();
  }

  /**
   * Handle timeout events. This forms the event loop and takes care
   * of all scheduling. This method should only be called if the Timer
   * Queue was constructed with the value of createInternalThread as
   * false.
   */
  public void handleEvents ()
  {
    if (!this.eventLoopRunning_)
      {
	// Set the flag indicating that the event loop is now running
	this.eventLoopRunning_ = true;

	TimeValue timeout = null;
	TimeValue earliest = null;

	for (;;)
	  {
	    synchronized (this.obj_) 
	      {
		earliest = this.earliestTime ();
		if (earliest != null)
		  timeout = TimeValue.minus (earliest, TimeValue.getTimeOfDay ());
		else
		  timeout = new TimeValue ();
		try
		  {
		    // Extract the earliest time from the queue and do a timed wait
		    this.obj_.timedWait (timeout);
		
		    // We have been notified. Check to see if we need to
		    // restart the wait with a different timeout
		    if (this.reset_)
		      {
			this.reset_ = false;
			this.obj_.condition (false);
			timeout = TimeValue.minus (this.earliestTime (), TimeValue.getTimeOfDay ());
		      }
		  }
		catch (TimeoutException e)
		  {
		    // Timeout occurred. Call handleTimeout on appropriate
		    // Event Handlers
		    this.dispatchHandlers ();
		  }
		catch (InterruptedException e)
		  {
		  }
	      }
	  }
      }
  }

  /**
   * Check if the queue is empty.
   *@return true if queue is empty, else false.
   */
  boolean isEmpty ()
  {
    return this.head_ == null;
  }

  /**
   * Get the node of the earliest node in the TimerQueue.
   *@return the time of the earlier node in the TimerQueue.
   */
  TimeValue earliestTime ()
  {
    synchronized (this.obj_)
      {
	if (!this.isEmpty ())
	  return this.head_.timerValue_;
	else
	  return null;
      }
  }

  /**
   * Schedule an <EventHandler> that will expire after <delta> amount
   * of time.  If it expires then <obj> is passed in as the value to
   * the <EventHandler>'s <handleTimeout> callback method. This method
   * returns a timer id that uniquely identifies the timer and can be
   * used to cancel the timer before it expires.
   *@param handler Event Handler that is to be scheduled with the timer
   *@param obj Object that is passed back to the Event Handler when
   * timeout occurs (Asynchronous Completion Token)
   *@param delta amount of time for which to schedule the timer
   *@return id of the timer scheduled
   */
  public int scheduleTimer (EventHandler handler,
			     Object obj,
			     TimeValue delta)
  {
    return this.scheduleTimer (handler, obj, delta, TimeValue.zero);
  }

  /**
   * Schedule an <EventHandler> that will expire after <delta> amount
   * of time.  If it expires then <obj> is passed in as the value to
   * the <EventHandler>'s <handleTimeout> callback method.  If
   * <interval> is != to <TimeValue.zero> then it is used to
   * reschedule the <EventHandler> automatically.  This method
   * returns a timer id that uniquely identifies the timer and can be
   * used to cancel the timer before it expires.
   *@param handler Event Handler that is to be scheduled with the timer
   *@param arg Object that is passed back to the Event Handler when
   * timeout occurs (Asynchronous Completion Token)
   *@param timeout amount of time for which to schedule the timer
   *@param interval amount of time to use to reschedule the timer
   *@return id of the timer scheduled
   */
  public int scheduleTimer (EventHandler handler,
			    Object arg,
			    TimeValue timeout,
			    TimeValue interval)
  {
    // Increment the sequence number (it will wrap around).
    this.timerId_++;
    TimeValue futureTime = TimeValue.plus (timeout, TimeValue.getTimeOfDay ());
    TimerNode node = new TimerNode (handler, 
				    arg,
				    futureTime,
				    interval, 
				    null,
				    this.timerId_);
    synchronized (this.obj_)
      {
	// Check if event loop is running. If it is not, then we can
	// just place it at the appropriate place in the queue and
	// don't need to do any notification. If event loop is
	// running, then check if the node is the first node in the
	// queue (either because the queue is empty or because the
	// time for the node is earlier than the currently scheduled
	// timer node).
	if (this.eventLoopRunning_ &&
	    (this.isEmpty () || futureTime.lessThan (this.earliestTime ())))
	  {
	    // Insert the node into (the beginning of) the queue to be
	    // scheduled. 
	    this.reschedule (node);

	    // Notify the waiting thread so that it can reschedule
	    // using the earliest timeout
	    this.obj_.notify ();
	  }
	else // Place in the appropriate position in the queue.
	  {
	    this.reschedule (node);
	  }
      }
    return this.timerId_;
  }


  /**
   * Cancel the single timer associated with <timerId>.
   *@param timerId id of the timer that needs to be cancelled.
   *@return Object that was passed in when timer was scheduled
   * (Asynchronous Completion Token). 
   */
  public Object cancelTimer (int timerId)
  {
    TimerNode prev = null;
    TimerNode curr = null;

    synchronized (this.obj_)
      {
	// Try to locate the TimerNode that matches the timerId.
	for (curr = this.head_; 
	     curr != null && curr.timerId_ != timerId;
	     curr = curr.next_)
	  prev = curr;

	if (curr != null)
	  {
	    if (prev == null)
	      this.head_ = curr.next_;
	    else
	      prev.next_ = curr.next_;

	    return curr.arg_;
	  }
      }
    return null;
  }

  /**
   * Cancel all timers associated with <Event Handler>.
   *@param handler Event Handler whose associated timers need to be cancelled.
   */
  public void cancelTimer (EventHandler handler)
  {
    TimerNode prev = null;
    TimerNode curr = this.head_;

    synchronized (this.obj_)
      {
	while (curr != null)
	  {
	    if (curr.handler_ == handler)
	      {
		if (prev == null)
		  {
		    this.head_ = curr.next_;
		    curr = this.head_;
		  }
		else
		  {
		    prev.next_ = curr.next_;
		    curr = prev.next_;
		  }
	      }
	    else
	      {
		prev = curr;
		curr = curr.next_;
	      }
	  }
      }
  }

  // Call handleTimeout() on all handlers whose timers have expired.
  private void dispatchHandlers ()
  {
    TimeValue currentTime = TimeValue.getTimeOfDay ();

    for (;;)
      {
	if (this.isEmpty () || this.earliestTime ().greaterThan (currentTime))
	  break; // There aren't any more timers eligible to expire.

	TimerNode expired = this.head_;
	EventHandler handler = expired.handler_;
	Object arg = expired.arg_;
	int result;
	
	this.head_ = this.head_.next_;
	
	// Check whether this is an interval timer.
	if (expired.interval_.greaterThan (TimeValue.zero))
	  {
	    // Make sure that we skip past values that have already
	    // "expired".
	    do
	      expired.timerValue_.plusEquals (expired.interval_);
	    while (expired.timerValue_.lessThanEqual (currentTime));
		   
	    // Since this is an interval timer, we need to reschedule
	    // it.
	    this.reschedule (expired);
	  }
	
	// Perform the callback.
	result = handler.handleTimeout (currentTime, arg);
	
	if (result == -1)
	  this.cancelTimer (handler);
      }
  }

  // Reschedule a TimerNode by inserting it at the appropriate
  // position in the queue.
  private void reschedule (TimerNode expired)
  {
    if (this.isEmpty () || 
	expired.timerValue_.lessThan (this.earliestTime ()))
      {
	expired.next_ = this.head_;
	this.head_ = expired;
	// Set the condition to true so that the waiting thread can be
	// notified and it can reschedule.
	this.obj_.condition (true);
	this.reset_ = true;
      }
    else
      {
	TimerNode prev = this.head_;
	TimerNode after  = this.head_.next_;

	// Locate the proper position in the queue. 

	while (after != null 
	       && expired.timerValue_.greaterThan (after.timerValue_))
	  {
	    prev = after;
	    after  = after.next_;
	  }

	expired.next_ = after;
	prev.next_  = expired;
      }
  }

  private WaitObject obj_ = new WaitObject ();
  // Synchronization object (as well as object to use to do wait on)

  private TimerNode head_; 
  // Pointer to linked list of TimerHandles. 

  private int timerId_;
  // Keeps track of the timer id that uniquely identifies each timer.
  // This id can be used to cancel a timer via the <cancel (int)>
  // method.

  private boolean reset_;
  // Flag indicating whether to start the wait again

  private boolean eventLoopRunning_;
  // Flag indicating whether the event loop is running or not
}