net.intelie.pipes.time.ManualSchedulerContext Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of pipes-api Show documentation
Show all versions of pipes-api Show documentation
Intelie Pipes' API classes and interfaces
package net.intelie.pipes.time;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
public class ManualSchedulerContext implements SchedulerContext {
public static final TaskRunner.Default DEFAULT_RUNNER = new TaskRunner.Default();
/**
* List of active tasks, ordered by the end of the current window. Non-active tasks are lazily cleaned.
*/
private final PriorityQueue queue = new PriorityQueue<>();
/**
* Temporary buffer to keep tasks before the scheduler's start time is determined.
*/
private List buffer = new ArrayList<>();
private final TaskRunner runner;
private volatile long events;
private volatile long startTime;
private long now;
private int currentIndex;
private volatile long nextTimestamp = 0;
public ManualSchedulerContext() {
this(null);
}
public ManualSchedulerContext(TaskRunner runner) {
this.runner = runner != null ? runner : DEFAULT_RUNNER;
this.startTime = -1;
}
@Override
public synchronized DefaultTaskHandle schedule(Period period, Task runnable) {
return scheduleAt(period, runnable, now);
}
public synchronized DefaultTaskHandle scheduleAt(Period period, Task runnable, long timestamp) {
DefaultTaskHandle handle = new DefaultTaskHandle(currentIndex++, events, period, runnable);
if (!started()) {
buffer.add(handle);
} else {
handle.start(timestamp);
queueAdd(handle);
}
return handle;
}
private void queueAdd(DefaultTaskHandle handle) {
queue.add(handle);
nextTimestamp = queue.peek().nextTimestamp();
}
public synchronized long notifyEvent() {
return ++events;
}
@Override
public long now() {
return now;
}
@Override
public boolean started() {
return startTime >= 0;
}
@Override
public long startTime() {
return startTime;
}
public synchronized long nextTaskTimestamp() {
DefaultTaskHandle top = peekActive();
if (top != null)
return top.nextTimestamp();
return Long.MAX_VALUE;
}
/**
* Returns the first active task in the priority queue. As a side effect, can discard non-active tasks.
* This function must be called within a synchronized block
*/
private DefaultTaskHandle peekActive() {
assert Thread.holdsLock(this);
while (!queue.isEmpty() && !queue.peek().active())
queue.poll();
return queue.peek();
}
@Override
public synchronized void start() {
if (!started()) {
startTime = now();
for (DefaultTaskHandle handle : buffer) {
handle.start(startTime);
queueAdd(handle);
}
// The buffer won't be used anymore; release it and let the GC take over.
buffer = null;
}
}
/**
* Equivalent to calling advanceTo(timestamp) followed by start().
*/
public void start(long timestamp) {
if (!started()) {
advanceTo(timestamp);
start();
}
}
/**
* Advances to the timestamp specified, running all the tasks that expired in the meantime.
*
* WARNING: this function will call the runnable passed in the constructor, which will call the tasks.
* Unless the caller controls all the tasks, it should avoid calling this function under a lock.
*/
public void advanceTo(long timestamp) {
//not sure if this "if" will break things, hope not
if (timestamp < nextTimestamp) {
updateNow(timestamp);
return;
}
internalFlush(timestamp, false);
}
/**
* Run the tasks up to a timestamp, or if the last parameter is true, remove all tasks and update the timestamp
* to the last task. This function must NOT be called within a synchronized block for this class.
*/
private void internalFlush(long timestamp, boolean last) {
while (true) {
List actions;
long run;
synchronized (this) {
if (last && buffer != null)
buffer.clear();
DefaultTaskHandle handle = peekActive();
if (handle == null || !last && handle.nextTimestamp() > timestamp)
break;
actions = new ArrayList<>();
run = handle.window.end();
while (handle != null && handle.window.end() == run) {
DefaultTaskHandle task = queue.poll();
if (!last || task.lastEvent != events) {
actions.add(new TaskAction(task.window.begin(), task.window.end(), task.runnable));
task.lastEvent = events;
}
task.window.moveNext();
if (!last)
queueAdd(task);
handle = peekActive();
}
now = run;
}
// Run any tasks outside the lock
assert !Thread.holdsLock(this);
if (!actions.isEmpty())
try {
runner.onTasks(run, actions);
} catch (Throwable e) {
Logger.getLogger(ManualSchedulerContext.class.getName())
.log(Level.WARNING, "Uncaught exception", e);
}
}
updateNow(timestamp);
}
private synchronized void updateNow(long timestamp) {
now = Math.max(now, timestamp);
}
public synchronized Set periods() {
Set periods = new LinkedHashSet<>();
for (DefaultTaskHandle handle : queue)
periods.add(handle.period);
if (buffer != null)
for (DefaultTaskHandle handle : buffer)
periods.add(handle.period);
return periods;
}
@Override
public synchronized void cancelAll() {
if (buffer != null)
buffer.clear();
queue.clear();
}
public void flushAndCancelAll() {
internalFlush(0, true);
}
public static class DefaultTaskHandle implements TaskHandle, Comparable {
private final int index;
private final Period period;
private Task runnable;
private volatile boolean active = true;
/**
* Iterator over the windows of this task's period.
*/
private PeriodIterator window;
private volatile long lastEvent;
public DefaultTaskHandle(int index, long lastEvent, Period period, Task runnable) {
this.index = index;
this.period = period;
this.runnable = runnable;
this.lastEvent = lastEvent;
}
@Override
public void cancel() {
active = false;
runnable = null; //GC-friendly
}
@Override
public int compareTo(DefaultTaskHandle that) {
if (this.nextTimestamp() != that.nextTimestamp())
return Long.compare(this.nextTimestamp(), that.nextTimestamp());
return Integer.compare(this.index, that.index);
}
@Override
public long nextTimestamp() {
return window.end();
}
public boolean active() {
return active;
}
/**
* Initializes this task's window iterator. Must be called within a synchronized block.
*/
public void start(long time) {
window = new PeriodIterator(time, true, period);
}
}
}