Files
smarttime/ts/smarttime.classes.cronmanager.ts

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();
}
}
}