net.intelie.pipes.time.DefaultSchedulerContext 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.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class DefaultSchedulerContext implements SchedulerContext {
private final ManualSchedulerContext context;
private final ScheduledExecutorService executor;
private final Clock clock;
private final long offset;
private ScheduledFuture future;
private long expectedNext;
/**
* When true, indicates that executeNext() is running but has dropped the lock.
*/
private boolean executingTasks;
public DefaultSchedulerContext(ScheduledExecutorService executor, Clock clock) {
this(executor, clock, 0);
}
public DefaultSchedulerContext(ScheduledExecutorService executor, Clock clock, long offset) {
this.context = new ManualSchedulerContext();
this.executor = executor;
this.clock = clock;
this.offset = offset;
this.expectedNext = Long.MAX_VALUE;
}
@Override
public synchronized TaskHandle schedule(Period period, Task runnable) {
final TaskHandle handle = context.scheduleAt(period, runnable, clock.now());
rescheduleNext();
return new TaskHandle() {
@Override
public long nextTimestamp() {
return handle.nextTimestamp();
}
@Override
public void cancel() {
handle.cancel();
rescheduleNext();
}
};
}
@Override
public long now() {
return clock.now() + offset;
}
@Override
public boolean started() {
return context.started();
}
@Override
public long startTime() {
return context.startTime();
}
@Override
public long nextTaskTimestamp() {
return expectedNext;
}
@Override
public synchronized void cancelAll() {
context.cancelAll();
rescheduleNext();
}
@Override
public synchronized void start() {
context.advanceTo(now());
context.start();
scheduleNext();
}
private synchronized void rescheduleNext() {
// If executingTasks is true, whoever sets it to false will complete the future and call scheduleNext() for us.
if (executingTasks) return;
if (future != null) {
// Optimistically attempt to prevent the thread from running. If already running, the check for expectedNext
// will make it exit.
future.cancel(false);
}
// Create a new future for the correct timestamp.
scheduleNext();
}
// This function must always be called within a synchronized block.
private void scheduleNext() {
assert Thread.holdsLock(this);
long now = now();
final long next = this.expectedNext = context.nextTaskTimestamp();
if (next == Long.MAX_VALUE) return;
future = executor.schedule(() -> executeNext(next), Math.max(next - now, 0), TimeUnit.MILLISECONDS);
}
private void executeNext(long next) {
synchronized (this) {
// There was a race condition with a reschedule, and more than one copy of this code is running.
if (executingTasks || next != expectedNext) return;
// We're about to drop the lock
executingTasks = true;
}
try {
// Run outside the lock
assert !Thread.holdsLock(this);
context.advanceTo(next);
} finally {
synchronized (this) {
// The lock has been reacquired
executingTasks = false;
scheduleNext();
}
}
}
}