Created
January 30, 2018 21:28
-
-
Save dumptruckman/8b5b0755f5bb2a53a6f631c5560af401 to your computer and use it in GitHub Desktop.
A lightweight framework for scheduling one-off tasks.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import javax.annotation.Nullable; | |
import java.time.LocalDateTime; | |
import java.time.temporal.ChronoUnit; | |
public class DelayedTaskManager implements Runnable { | |
private static final long PRECISION_CHECK_TIME_MS = 10000; | |
private final DelayedTaskStore taskStore; | |
private final DelayedTaskRunner taskRunner; | |
@Nullable | |
private volatile DelayedTask nextTask; | |
DelayedTaskManager(DelayedTaskStore taskStore, DelayedTaskRunner taskRunner) { | |
this.taskStore = taskStore; | |
this.taskRunner = taskRunner; | |
nextTask = taskStore.getNextTask(); | |
} | |
/** | |
* Adds a task to be executed at a later time. | |
* WARNING: Do not call this from a task or there will be a deadlock. | |
* | |
* @param expirationTime the time at which the task should be executed. | |
* @param task the task to execute. | |
*/ | |
public void addTask(LocalDateTime expirationTime, Runnable task) { | |
synchronized (this) { | |
DelayedTask delayedTask = taskStore.createTask(expirationTime, task); | |
if (nextTask != null && nextTask.getExpirationTime().isAfter(delayedTask.getExpirationTime())) { | |
nextTask = delayedTask; | |
} | |
} | |
} | |
@Override | |
public void run() { | |
while (true) { | |
synchronized (this) { | |
if (nextTask == null) { | |
nextTask = taskStore.getNextTask(); | |
} | |
if (nextTask != null) { | |
LocalDateTime expiration = nextTask.getExpirationTime(); | |
LocalDateTime now = LocalDateTime.now(); | |
if (expiration.isAfter(now)) { | |
// Wait until task is due or someone adds a task. | |
sleep(expiration, now); | |
} else { | |
performTask(); | |
} | |
} else { | |
// Wait until someone adds a task. | |
sleep(); | |
} | |
} | |
} | |
} | |
private void sleep() { | |
try { | |
wait(); | |
} catch (InterruptedException e) { | |
e.printStackTrace(); | |
} | |
} | |
private void sleep(LocalDateTime expiration, LocalDateTime now) { | |
long waitDuration = getWaitDuration(expiration, now); | |
try { | |
wait(waitDuration); | |
} catch (InterruptedException e) { | |
e.printStackTrace(); | |
} | |
} | |
private long getWaitDuration(LocalDateTime expiration, LocalDateTime now) { | |
long diff = expiration.until(now, ChronoUnit.MILLIS); | |
if (diff <= PRECISION_CHECK_TIME_MS) { | |
return diff; | |
} else { | |
return diff - PRECISION_CHECK_TIME_MS; | |
} | |
} | |
private void performTask() { | |
taskRunner.runTask(nextTask); | |
taskStore.markCompleted(nextTask); | |
nextTask = null; | |
} | |
public static class DelayedTask implements Comparable<DelayedTask> { | |
private final int taskId; | |
private final Runnable task; | |
private final LocalDateTime expirationTime; | |
DelayedTask(int taskId, Runnable task, LocalDateTime expirationTime) { | |
this.taskId = taskId; | |
this.task = task; | |
this.expirationTime = expirationTime; | |
} | |
public int getTaskId() { | |
return taskId; | |
} | |
public Runnable getTask() { | |
return task; | |
} | |
public LocalDateTime getExpirationTime() { | |
return expirationTime; | |
} | |
@Override | |
public int compareTo(DelayedTask o) { | |
return getExpirationTime().compareTo(o.getExpirationTime()); | |
} | |
@Override | |
public boolean equals(Object o) { | |
if (this == o) return true; | |
if (!(o instanceof DelayedTask)) return false; | |
final DelayedTask that = (DelayedTask) o; | |
return getTaskId() == that.getTaskId(); | |
} | |
@Override | |
public int hashCode() { | |
return getTaskId(); | |
} | |
} | |
public interface DelayedTaskStore { | |
/** | |
* Retrieves the task that will be the first to expire or null if no tasks remain. | |
*/ | |
@Nullable | |
DelayedTask getNextTask(); | |
/** | |
* Notifies the backing store that the given task has been completed. | |
*/ | |
void markCompleted(DelayedTask task); | |
/** | |
* Adds the given task to the backing store task store and returns the DelayedTask object | |
* that represents the task in the store. | |
*/ | |
DelayedTask createTask(LocalDateTime expiration, Runnable task); | |
} | |
public interface DelayedTaskRunner { | |
/** | |
* Runs the given task. This or the task run should never add more tasks or there will be a deadlock. | |
*/ | |
void runTask(DelayedTask task); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment