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;
|
package fr.pandacube.lib.core.backup;
|
||||||
|
|
||||||
import fc.cron.CronExpression;
|
import fc.cron.CronExpression;
|
||||||
|
import fr.pandacube.lib.core.cron.CronScheduler;
|
||||||
import fr.pandacube.lib.util.FileUtils;
|
import fr.pandacube.lib.util.FileUtils;
|
||||||
import fr.pandacube.lib.util.Log;
|
import fr.pandacube.lib.util.Log;
|
||||||
import net.md_5.bungee.api.ChatColor;
|
import net.md_5.bungee.api.ChatColor;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.ZoneId;
|
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.time.format.DateTimeFormatterBuilder;
|
import java.time.format.DateTimeFormatterBuilder;
|
||||||
@ -285,8 +284,6 @@ public abstract class BackupProcess implements Comparable<BackupProcess>, Runnab
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return parsedScheduling.nextTimeAfter(ZonedDateTime.ofInstant(Instant.ofEpochMilli(dirtySince), ZoneId.systemDefault()))
|
return CronScheduler.getNextTime(parsedScheduling, dirtySince);
|
||||||
.toInstant()
|
|
||||||
.toEpochMilli();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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