/**************************************************************************
* Parts copyright (c) 2001 by Punch Telematix. All rights reserved. *
* Parts copyright (c) 2009, 2011 by /k/ Embedded Java Solutions. *
* All rights reserved. *
* *
* Redistribution and use in source and binary forms, with or without *
* modification, are permitted provided that the following conditions *
* are met: *
* 1. Redistributions of source code must retain the above copyright *
* notice, this list of conditions and the following disclaimer. *
* 2. Redistributions in binary form must reproduce the above copyright *
* notice, this list of conditions and the following disclaimer in the *
* documentation and/or other materials provided with the distribution. *
* 3. Neither the name of Punch Telematix or of /k/ Embedded Java Solutions*
* nor the names of other contributors may be used to endorse or promote*
* products derived from this software without specific prior written *
* permission. *
* *
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED *
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF *
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. *
* IN NO EVENT SHALL PUNCH TELEMATIX, /K/ EMBEDDED JAVA SOLUTIONS OR OTHER *
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, *
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, *
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR *
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF *
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING *
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS *
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
**************************************************************************/
package java.util;
import java.lang.ref.WeakReference;
import wonka.vm.Heartbeat;
class TimerThread extends Thread implements Comparator {
private TreeSet tasks;
boolean cancelled;
boolean waiting;
long waitTime;
private long savedtime;
/**
** we use a WeakReference to reference the Timer. When all references to the timer are gone, the thread can terminate.
*/
private WeakReference timerReference;
// Only start collecting timer drift on the first active use of this class
static {
Heartbeat.collectTimeOffset();
}
TimerThread(Timer timer, boolean daemon){
super("Thread for timer:"+timer);
timerReference = new WeakReference(timer);
tasks = new TreeSet(this);
this.setDaemon(daemon);
this.start();
}
/**
* Compare two timers o and t.
* <ul><li>If o == t then we return 0.
* <li>Otherwise if o and t have different start times (allowing for any
* offset) then we return +/-1 based on the start times.
* <li>If the start times are the * same then we return +/-1 based on the
* hash code: this is arbitrary, but consistent. (It is important that we
* only return 0 if o.equals(t) would return true, otherwise we screw up
* our TreeSet).
*/
public int compare(Object o, Object t){
if (o == t) {
return 0;
}
TimerTask task_o = (TimerTask)o;
TimerTask task_t = (TimerTask)t;
long diff = (task_o.startTime - task_o.savedoffset) - (task_t.startTime- task_t.savedoffset);
if (diff != 0L) {
return diff < 0L ? -1 : 1 ;
}
return o.hashCode() < t.hashCode() ? -1 : 1 ;
}
public void run(){
//System.out.println("STARTING TIMERTHREAD " + this);
TimerTask task;
long time0; // System.currentTimeMillis()
long time1; // earliest estimate of corrected time
long time2; // latest estimate of corrected time
long offset;
while(true){
synchronized(this){
try {
if(cancelled){
//System.out.println("TIMERTHREAD "+ this + ": Thread is cancelled TIMERTHREAD");
break;
}
if(tasks.isEmpty()){
//STATE 1: no tasks scheduled.
//System.out.println("TIMERTHREAD "+ this + ": queue is empty");
if(timerReference.get() != null){
//we use this boolean to indicate when the timer The is waiting
//at that point it is ok for the timer to call a notifyAll without
//disturbing any timer tasks ...
//System.out.println("TIMERTHREAD " + this +": timer is alive");
waiting = true;
this.wait();
waiting = false;
}
else {
//no task scheduled and the 'Timer' is collected. We stop the tread.
//System.out.println("TIMERTHREAD " + this +": timer is gone");
cancelled = true;
}
continue;
}
else {
//STATE 2: tasks are pending ...
//System.out.println("TIMERTHREAD " + this +": queue is not empty");
task = (TimerTask)tasks.first();
if(task.cancelled){
tasks.remove(task);
continue;
}
offset = task.absolute ? 0 : Heartbeat.getTimeOffset();
if (offset > task.savedoffset) {
//System.out.println("TIMERTHREAD " + this + ": offset has increased, " + task.savedoffset + " -> " + offset);
}
else if (offset < task.savedoffset) {
//System.out.println("TIMERTHREAD " + this + ": offset has decreased, " + task.savedoffset + " -> " + offset);
}
time0 = System.currentTimeMillis();
if (offset > task.savedoffset) {
time1 = time0 - offset;
time2 = time0 - task.savedoffset;
}
else {
time1 = time0 - task.savedoffset;
time2 = time0 - offset;
}
if (time0 < savedtime) {
//System.out.println("TIMERTHREAD " + this + ": time went backwards by " + (savedtime - time0));
time1 -= savedtime - time0;
}
if (time2 < task.startTime) {
long nap = task.startTime - time2;
if (nap > 10000) {
nap = 10000;
}
//it is not yet time todo the task
//System.out.println("TIMERTHREAD " + this +": task is not ready to run waiting " + nap);
waiting = true;
this.wait(nap);
waiting = false;
continue;
}
//System.out.println("TIMERTHREAD " + this +": task is ready to run " + (task.startTime - time2));
tasks.remove(task);
// if task is periodic, subsequent schedulings are considered to be relative
task.absolute = false;
savedtime = time0;
}
}
catch(InterruptedException ie){
//System.out.println("TIMERTHREAD " + this +": thread is interrupted");
waiting = false;
continue;
}
}
/**
** The task cannot be run in a synchronized block ...
** We leave the chance for other to add tasks to timer.
*/
//System.out.println("TIMERTHREAD " + this +": running task "+task);
task.run();
synchronized(this){
//System.out.println("TIMERTHREAD " + this +": cleaning up task "+task);
if(task.period != -1 && !task.cancelled){
//System.out.println("TIMERTHREAD " + this +": putting task back in "+task);
//System.out.println("TIMERTHREAD " + this + (task.fixed ? " fixed rate scheduling, next = " + (task.period + task.startTime) : " free scheduling, next = " + (task.period + time1)));
task.startTime = task.period + (task.fixed ? task.startTime : time1);
task.savedoffset = offset;
tasks.add(task);
}
else {
//System.out.println("TIMERTHREAD " + this +": cancelling task "+task);
task.cancelled = true;
}
}
}
}
void schedule(TimerTask task, long delay, long period, boolean fixedRate){
if(delay < 0){
throw new IllegalArgumentException("negative delay is not allowed");
}
// System.out.println("System.currentTimeMillis() = " + System.currentTimeMillis() + ", delay = " + delay + " => schedule at " + (delay + System.currentTimeMillis()));
scheduleAtTime(task, delay + System.currentTimeMillis(), period, fixedRate, false);
}
synchronized void scheduleAtTime(TimerTask task, long time, long period, boolean fixedRate, boolean absolute){
if(time < 0){
throw new IllegalArgumentException("negative time is not allowed");
}
if(task.startTime != -1 || task.cancelled){
throw new IllegalStateException("task cannot be scheduled");
}
if(cancelled || !this.isAlive()){
throw new IllegalStateException("timer cannot schedule task "+task);
}
//System.out.println("Adding task to timer "+task);
//System.out.println("Setting " + task + ".startTime to " + time);
task.absolute = absolute;
task.savedoffset = absolute ? 0 : Heartbeat.getTimeOffset();
task.startTime = time - task.savedoffset;
task.period = period;
task.fixed = fixedRate;
tasks.add(task);
if(waiting){
//System.out.println("Notifying timer thread");
this.notifyAll();
}
}
}