107 lines
3.0 KiB
TypeScript
107 lines
3.0 KiB
TypeScript
import * as plugins from './smarttime.plugins.js';
|
|
import { CronJob, type TJobFunction } from './smarttime.classes.cronjob.js';
|
|
|
|
export class CronManager {
|
|
public executionTimeout: plugins.smartdelay.Timeout<void>;
|
|
|
|
public status: 'started' | 'stopped' = 'stopped';
|
|
public cronjobs = new plugins.lik.ObjectMap<CronJob>();
|
|
|
|
private cycleWakeDeferred: plugins.smartpromise.Deferred<void> | null = null;
|
|
|
|
constructor() {}
|
|
|
|
/**
|
|
* Resolves the current wake deferred, causing the sleeping cycle
|
|
* to immediately recalculate instead of waiting for its timeout.
|
|
*/
|
|
private wakeCycle() {
|
|
if (this.cycleWakeDeferred && this.cycleWakeDeferred.status === 'pending') {
|
|
this.cycleWakeDeferred.resolve();
|
|
}
|
|
}
|
|
|
|
public addCronjob(cronIdentifierArg: string, cronFunctionArg: TJobFunction) {
|
|
const newCronJob = new CronJob(this, cronIdentifierArg, cronFunctionArg);
|
|
this.cronjobs.add(newCronJob);
|
|
if (this.status === 'started') {
|
|
newCronJob.start();
|
|
this.wakeCycle();
|
|
}
|
|
|
|
return newCronJob;
|
|
}
|
|
|
|
public removeCronjob(cronjobArg: CronJob) {
|
|
cronjobArg.stop();
|
|
this.cronjobs.remove(cronjobArg);
|
|
if (this.status === 'started') {
|
|
this.wakeCycle();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* starts the cronjob
|
|
*/
|
|
public start() {
|
|
if (this.status !== 'started') {
|
|
this.status = 'started';
|
|
for (const cronJob of this.cronjobs.getArray()) {
|
|
cronJob.start();
|
|
}
|
|
this.runCronCycle();
|
|
}
|
|
}
|
|
|
|
private async runCronCycle() {
|
|
while (this.status === 'started') {
|
|
// Create a fresh wake signal for this iteration
|
|
this.cycleWakeDeferred = new plugins.smartpromise.Deferred<void>();
|
|
|
|
// Check all cronjobs and find the soonest next execution
|
|
let soonestMs = Infinity;
|
|
for (const cronJob of this.cronjobs.getArray()) {
|
|
cronJob.checkExecution();
|
|
const msToNext = cronJob.getTimeToNextExecution();
|
|
if (msToNext < soonestMs) {
|
|
soonestMs = msToNext;
|
|
}
|
|
}
|
|
|
|
// Sleep until the next job is due or until woken by a lifecycle event
|
|
if (soonestMs < Infinity && soonestMs > 0) {
|
|
this.executionTimeout = new plugins.smartdelay.Timeout(soonestMs);
|
|
await Promise.race([
|
|
this.executionTimeout.promise,
|
|
this.cycleWakeDeferred.promise,
|
|
]);
|
|
// Cancel the timeout to avoid lingering timers
|
|
this.executionTimeout.cancel();
|
|
} else if (soonestMs <= 0) {
|
|
// Job is overdue, loop immediately to execute it
|
|
continue;
|
|
} else {
|
|
// No jobs — wait indefinitely until woken by addCronjob or stop
|
|
await this.cycleWakeDeferred.promise;
|
|
}
|
|
}
|
|
this.cycleWakeDeferred = null;
|
|
}
|
|
|
|
/**
|
|
* stops all cronjobs
|
|
*/
|
|
public stop() {
|
|
if (this.status === 'started') {
|
|
this.status = 'stopped';
|
|
if (this.executionTimeout) {
|
|
this.executionTimeout.cancel();
|
|
}
|
|
this.wakeCycle();
|
|
}
|
|
for (const cron of this.cronjobs.getArray()) {
|
|
cron.stop();
|
|
}
|
|
}
|
|
}
|