Alachisoft.NCache.Common.Threading.TimeScheduler Maven / Gradle / Ivy
package Alachisoft.NCache.Common.Threading;
import Alachisoft.NCache.Common.Logger.ILogger;
/**
* The scheduler supports varying scheduling intervals by asking the task every time for its next preferred scheduling interval. Scheduling can either be fixed-delay or
* fixed-rate. In fixed-delay scheduling, the task's new schedule is calculated as:
new_schedule = time_task_starts + scheduling_interval In fixed-rate
* scheduling, the next schedule is calculated as:
new_schedule = time_task_was_supposed_to_start + scheduling_interval
The scheduler internally holds a queue of
* tasks sorted in ascending order according to their next execution time. A task is removed from the queue if it is cancelled, i.e. if TimeScheduler.Task.isCancelled()
* returns true.
Initially, the scheduler is in SUSPENDed mode, start() need not be called: if a task is added, the scheduler gets started automatically.
* Calling start() starts the scheduler if it's suspended or stopped else has no effect. Once stop() is called, added tasks will not restart it: start()
* has to be called to restart the scheduler.
*
*
* Fixed-delay and fixed-rate single thread scheduler
Author: Chris Koiak, Bela Ban
Date: 12/03/2003
*/
public class TimeScheduler implements Runnable {
/**
* Needed in case all tasks have been cancelled and we are still waiting on the schedule time of the task at the top
*
* Regular wake-up intervals for scheduler
*/
private static final long TICK_INTERVAL = 1000;
/**
* TimeScheduler thread name
*/
private static final String THREAD_NAME = "TimeScheduler.Thread";
private static ILogger _logger = null;
/**
* The scheduler thread
*/
private Thread thread = null;
/**
* The thread's running state
*/
private State thread_state = State.SUSPEND;
/**
* Time that task queue is empty before suspending the scheduling thread
*/
private long suspend_interval;
/**
* Sorted list of
* IntTask
s
*/
private EventQueue queue;
/**
* Create a scheduler that executes tasks in dynamically adjustable intervals
*
* @param suspend_interval The time that the scheduler will wait for at least one task to be placed in the task queue before suspending the scheduling thread
*/
public TimeScheduler(long suspend_interval) {
queue = new EventQueue();
this.suspend_interval = suspend_interval;
}
/**
* Create a scheduler that executes tasks in dynamically adjustable intervals
*/
public TimeScheduler() {
this(2000);
}
/**
* Set the thread state to running, create and start the thread
*/
private void _start() {
synchronized (this) {
if (thread_state != State.DISPOSED) {
thread_state = State.RUN;
thread = new Thread(this);
thread.setName(THREAD_NAME);
thread.setDaemon(true);
thread.start();
}
}
}
/**
* Restart the suspended thread
*/
private void _unsuspend() {
_start();
}
/**
* Set the thread state to suspended
*/
private void _suspend() {
synchronized (this) {
if (thread_state != State.DISPOSED) {
thread_state = State.SUSPEND;
thread = null;
}
}
}
/**
* Set the thread state to stopping
*/
private void _stopping() {
synchronized (this) {
if (thread_state != State.DISPOSED) {
thread_state = State.STOPPING;
}
}
}
/**
* Set the thread state to stopped
*/
private void _stop() {
synchronized (this) {
if (thread_state != State.DISPOSED) {
thread_state = State.STOP;
thread = null;
}
}
}
/**
* Get the first task, if the running time hasn't been reached then wait a bit and retry. Else reschedule the task and then run it. *
*
* If the task queue is empty, sleep until a task comes in or if slept for too long, suspend the thread.
*/
@Override
public void run() {
long elapsedTime;
try {
while (true) {
synchronized (this) {
if (thread == null) {
return;
}
}
Task task = null;
boolean lockReAcquired = true;
synchronized (queue) {
if (queue.getIsEmpty()) {
//Monitor.Wait(queue, (int) suspend_interval);
long startTime = System.nanoTime();
Monitor.wait(queue); //queue.wait();
long endTime = System.nanoTime();
lockReAcquired = (endTime - startTime) < suspend_interval;
}
// if (queue.getIsEmpty())
// {
// _suspend();
// return;
// }
if (lockReAcquired) {
QueuedEvent e = queue.Peek();
if (e != null) {
synchronized (e) {
task = e.Task;
if (task.IsCancelled()) {
queue.Pop();
continue;
}
elapsedTime = e.getElapsedTime();
if (elapsedTime >= e.getInterval()) {
// Reschedule the task
queue.Pop();
if (e.ReQueue()) {
queue.Push(e);
}
}
}
if (elapsedTime < e.getInterval()) {
//Monitor.Wait(queue, (int) (e.getInterval() - elapsedTime));
Monitor.wait(queue, e.getInterval() - elapsedTime);// queue.wait((e.getInterval() - elapsedTime));
continue;
}
}
}
}
synchronized (this) {
if (queue.getIsEmpty() && !lockReAcquired) {
_suspend();
return;
}
}
try {
if (task != null) {
task.Run();
}
} catch (Exception ex) {
//System.err.println("TimeScheduler._run() " + ex.getMessage());
// _logger.Error("TimeScheduler._run()", ex.toString());
}
}
} catch (InterruptedException ex) {
//System.err.println("TimeScheduler._run() " + ex.getMessage());
// _logger.Error("TimeScheduler._run()", ex.getMessage());
}
}
/**
* Relative Scheduling true:
Task is rescheduled relative to the last time it actually started execution
false:
Task is
* scheduled relative to its last execution schedule. This has the effect that the time between two consecutive executions of the task remains the same.
*
*
* Add a task for execution at adjustable intervals
*
* @param t The task to execute
* @param relative Use relative scheduling
*/
public final void AddTask(Task t, boolean relative) {
long interval;
synchronized (this) {
if (thread_state == State.DISPOSED) {
return;
}
if ((interval = t.GetNextInterval()) < 0) {
return;
}
queue.Push(new QueuedEvent(t));
switch (thread_state) {
case RUN: //Monitor.PulseAll(queue);
break;
case SUSPEND:
_unsuspend();
break;
case STOPPING:
break;
case STOP:
break;
}
}
}
/**
* Add a task for execution at adjustable intervals
*
* @param t The task to execute
*/
public final void AddTask(Task t) {
AddTask(t, true);
}
/**
* Start the scheduler, if it's suspended or stopped
*/
public final void Start() {
synchronized (this) {
switch (thread_state) {
case DISPOSED:
break;
case RUN:
break;
case SUSPEND:
_unsuspend();
break;
case STOPPING:
break;
case STOP:
_start();
break;
}
}
}
/**
* Stop the scheduler if it's running. Switch to stopped, if it's suspended. Clear the task queue.
*/
public final void Stop() throws InterruptedException {
// i. Switch to STOPPING, interrupt thread
// ii. Wait until thread ends
// iii. Clear the task queue, switch to STOPPED,
synchronized (this) {
switch (thread_state) {
case RUN:
_stopping();
break;
case SUSPEND:
_stop();
return;
case STOPPING:
return;
case STOP:
return;
case DISPOSED:
return;
}
thread.interrupt();
}
thread.join();
synchronized (this) {
queue.Clear();
_stop();
}
}
/**
* Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
*/
public void dispose() throws InterruptedException {
Thread tmp = null;
synchronized (this) {
if (thread_state == State.DISPOSED) {
return;
}
tmp = thread;
thread_state = State.DISPOSED;
thread = null;
if (tmp != null) {
tmp.interrupt();
}
}
if (tmp != null) {
tmp.join();
//for (int i = 0; i < queue.Count; i++)
//{
// IDisposable disposable = queue.Pop().Task as IDisposable;
// disposable.Dispose();
//}
queue.Clear();
}
}
private enum State {
/**
* State Constant
*/
RUN(0),
/**
* State Constant
*/
SUSPEND(1),
/**
* State Constant
*/
STOPPING(2),
/**
* State Constant
*/
STOP(3),
/**
* State Constant
*/
DISPOSED(4);
private static java.util.HashMap mappings;
private int intValue;
private State(int value) {
intValue = value;
State.getMappings().put(value, this);
}
private static java.util.HashMap getMappings() {
if (mappings == null) {
synchronized (State.class) {
if (mappings == null) {
mappings = new java.util.HashMap();
}
}
}
return mappings;
}
public static State forValue(int value) {
return getMappings().get(value);
}
public int getValue() {
return intValue;
}
}
/**
* The interface that submitted tasks must implement
*/
public interface Task {
/**
* Returns true if task is cancelled and shouldn't be scheduled again
*
* @return
*/
boolean IsCancelled();
/**
* The next schedule interval
*
* @return The next schedule interval
*/
long GetNextInterval();
/**
* Execute the task
*/
void Run();
}
}