Cron scheduler in Java, using the CronExpression library.
This commit is contained in:
parent
c5c6181e15
commit
4c31c0d6e4
@ -1,13 +1,12 @@
|
||||
package fr.pandacube.lib.core.backup;
|
||||
|
||||
import fc.cron.CronExpression;
|
||||
import fr.pandacube.lib.core.cron.CronScheduler;
|
||||
import fr.pandacube.lib.util.FileUtils;
|
||||
import fr.pandacube.lib.util.Log;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeFormatterBuilder;
|
||||
@ -285,8 +284,6 @@ public abstract class BackupProcess implements Comparable<BackupProcess>, Runnab
|
||||
return 0;
|
||||
}
|
||||
|
||||
return parsedScheduling.nextTimeAfter(ZonedDateTime.ofInstant(Instant.ofEpochMilli(dirtySince), ZoneId.systemDefault()))
|
||||
.toInstant()
|
||||
.toEpochMilli();
|
||||
return CronScheduler.getNextTime(parsedScheduling, dirtySince);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,179 @@
|
||||
package fr.pandacube.lib.core.cron;
|
||||
|
||||
import fc.cron.CronExpression;
|
||||
import fr.pandacube.lib.util.Log;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
// TODO Add support for persisted last execution timestamps
|
||||
/**
|
||||
* Application wide task scheduler using Cron expression.
|
||||
*/
|
||||
public class CronScheduler {
|
||||
|
||||
private static final Object lock = new Object();
|
||||
|
||||
private static final List<CronTask> tasks = new ArrayList<>();
|
||||
private static final Map<String, CronTask> tasksById = new HashMap<>();
|
||||
|
||||
|
||||
|
||||
private static volatile boolean init = false;
|
||||
private static void init() {
|
||||
synchronized (CronScheduler.class) {
|
||||
if (init)
|
||||
return;
|
||||
init = true;
|
||||
loadLastRuns();
|
||||
Thread t = new Thread(CronScheduler::run, "Pandalib CronScheduler Thread");
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static void run() {
|
||||
synchronized (lock) {
|
||||
for (;;) {
|
||||
long wait = 0;
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
if (!tasks.isEmpty()) {
|
||||
CronTask next = tasks.get(0);
|
||||
if (next.nextRun <= now) {
|
||||
next.runAsync();
|
||||
setLastRun(next.taskId, next.nextRun);
|
||||
onTaskUpdate(false);
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
wait = next.nextRun - now;
|
||||
}
|
||||
}
|
||||
try {
|
||||
lock.wait(wait, 0);
|
||||
} catch (InterruptedException e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Schedule a task.
|
||||
* @param taskId the id of the task.
|
||||
* @param cronExpression the scheduling of the task. May use seconds (6 values) or not (5 values)
|
||||
* @param task the task to run.
|
||||
*/
|
||||
public static void schedule(String taskId, String cronExpression, Runnable task) {
|
||||
init();
|
||||
synchronized (lock) {
|
||||
long lastRun = getLastRun(taskId);
|
||||
|
||||
CronTask existing = getTask(taskId);
|
||||
if (existing != null) {
|
||||
// replacing task
|
||||
removeTask(taskId);
|
||||
}
|
||||
|
||||
CronExpression cron = new CronExpression(cronExpression, cronExpression.split("\\s+").length == 6);
|
||||
addTask(new CronTask(taskId, task, cron, lastRun));
|
||||
onTaskUpdate(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel a scheduled task.
|
||||
* Will not stop a current execution of the task. If the task does not exists, it will not do anything.
|
||||
* @param taskId the id of the task to cancel.
|
||||
*/
|
||||
public static void unSchedule(String taskId) {
|
||||
synchronized (lock) {
|
||||
CronTask existing = getTask(taskId);
|
||||
if (existing != null) {
|
||||
removeTask(taskId);
|
||||
onTaskUpdate(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static void onTaskUpdate(boolean notify) {
|
||||
synchronized (lock) {
|
||||
tasks.sort(Comparator.comparing(t -> t.nextRun));
|
||||
if (notify) {
|
||||
Log.info("Scheduler notified.");
|
||||
lock.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void addTask(CronTask nextTask) {
|
||||
synchronized (lock) {
|
||||
tasks.add(nextTask);
|
||||
tasksById.put(nextTask.taskId, nextTask);
|
||||
}
|
||||
}
|
||||
|
||||
private static CronTask getTask(String taskId) {
|
||||
synchronized (lock) {
|
||||
return tasksById.get(taskId);
|
||||
}
|
||||
}
|
||||
|
||||
private static void removeTask(String taskId) {
|
||||
synchronized (lock) {
|
||||
tasks.remove(tasksById.remove(taskId));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private static final Map<String, Long> savedLastRun = new LinkedHashMap<>();
|
||||
|
||||
private static void saveLastRuns() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
private static void loadLastRuns() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/* package */ static void setLastRun(String taskId, long lastRun) {
|
||||
savedLastRun.put(taskId, lastRun);
|
||||
saveLastRuns();
|
||||
}
|
||||
|
||||
private static long getLastRun(String taskId) {
|
||||
return savedLastRun.getOrDefault(taskId, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tells when the next time is scheduled, according to the provided cron expression, strictly after the provided time.
|
||||
* @param expr the cron expression to use to determine the schedule time.
|
||||
* @param lastTime the start search time.
|
||||
* @return the time of the next execution of the task.
|
||||
*/
|
||||
public static long getNextTime(CronExpression expr, long lastTime) {
|
||||
return expr.nextTimeAfter(ZonedDateTime.ofInstant(Instant.ofEpochMilli(lastTime), ZoneId.systemDefault()))
|
||||
.toInstant()
|
||||
.toEpochMilli();
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package fr.pandacube.lib.core.cron;
|
||||
|
||||
import fc.cron.CronExpression;
|
||||
|
||||
/* package */ class CronTask {
|
||||
/**
|
||||
* The id of the task, used to persist its last run.
|
||||
*/
|
||||
/* package */ final String taskId;
|
||||
/**
|
||||
* The task to run.
|
||||
*/
|
||||
/* package */ final Runnable task;
|
||||
/**
|
||||
* The cron expression telling when to run the task.
|
||||
*/
|
||||
/* package */ final CronExpression scheduling;
|
||||
/**
|
||||
* Millis timestamp of the previous run. Must be saved.
|
||||
*/
|
||||
/* package */ long lastRun;
|
||||
/**
|
||||
* Millis timestamp of the next run.
|
||||
*/
|
||||
/* package */ long nextRun;
|
||||
|
||||
|
||||
|
||||
/* package */ CronTask(String taskId, Runnable task, CronExpression scheduling, long lastRun) {
|
||||
this.taskId = taskId;
|
||||
this.task = task;
|
||||
this.scheduling = scheduling;
|
||||
this.lastRun = lastRun;
|
||||
updateNextRun();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* package */ void updateNextRun() {
|
||||
nextRun = CronScheduler.getNextTime(scheduling, lastRun);
|
||||
}
|
||||
|
||||
|
||||
/* package */ void runAsync() {
|
||||
Thread t = new Thread(task, "Pandalib CronTask " + taskId);
|
||||
t.start();
|
||||
lastRun = nextRun;
|
||||
updateNextRun();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user