mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-11 20:43:42 +00:00
refactor(cron): dedupe next-run recompute paths
This commit is contained in:
@@ -39,14 +39,8 @@ describe("issue #17852 - daily cron jobs should not skip days", () => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
it("recomputeNextRunsForMaintenance should NOT advance past-due nextRunAtMs", () => {
|
function createDailyThreeAmJob(threeAM: number): CronJob {
|
||||||
// Simulate: job scheduled for 3:00 AM, timer processing happens at 3:00:01
|
return {
|
||||||
// The job was NOT executed in this tick (e.g., it became due between
|
|
||||||
// findDueJobs and the post-execution block).
|
|
||||||
const threeAM = Date.parse("2026-02-16T03:00:00.000Z");
|
|
||||||
const now = threeAM + 1_000; // 3:00:01
|
|
||||||
|
|
||||||
const job: CronJob = {
|
|
||||||
id: "daily-job",
|
id: "daily-job",
|
||||||
name: "daily 3am",
|
name: "daily 3am",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@@ -56,9 +50,19 @@ describe("issue #17852 - daily cron jobs should not skip days", () => {
|
|||||||
createdAtMs: threeAM - DAY_MS,
|
createdAtMs: threeAM - DAY_MS,
|
||||||
updatedAtMs: threeAM - DAY_MS,
|
updatedAtMs: threeAM - DAY_MS,
|
||||||
state: {
|
state: {
|
||||||
nextRunAtMs: threeAM, // Past-due by 1 second
|
nextRunAtMs: threeAM,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
it("recomputeNextRunsForMaintenance should NOT advance past-due nextRunAtMs", () => {
|
||||||
|
// Simulate: job scheduled for 3:00 AM, timer processing happens at 3:00:01
|
||||||
|
// The job was NOT executed in this tick (e.g., it became due between
|
||||||
|
// findDueJobs and the post-execution block).
|
||||||
|
const threeAM = Date.parse("2026-02-16T03:00:00.000Z");
|
||||||
|
const now = threeAM + 1_000; // 3:00:01
|
||||||
|
|
||||||
|
const job = createDailyThreeAmJob(threeAM);
|
||||||
|
|
||||||
const state = createMockState([job], now);
|
const state = createMockState([job], now);
|
||||||
recomputeNextRunsForMaintenance(state);
|
recomputeNextRunsForMaintenance(state);
|
||||||
@@ -75,19 +79,7 @@ describe("issue #17852 - daily cron jobs should not skip days", () => {
|
|||||||
const threeAM = Date.parse("2026-02-16T03:00:00.000Z");
|
const threeAM = Date.parse("2026-02-16T03:00:00.000Z");
|
||||||
const now = threeAM + 1_000; // 3:00:01
|
const now = threeAM + 1_000; // 3:00:01
|
||||||
|
|
||||||
const job: CronJob = {
|
const job = createDailyThreeAmJob(threeAM);
|
||||||
id: "daily-job",
|
|
||||||
name: "daily 3am",
|
|
||||||
enabled: true,
|
|
||||||
schedule: { kind: "cron", expr: "0 3 * * *", tz: "UTC" },
|
|
||||||
payload: { kind: "systemEvent", text: "daily task" },
|
|
||||||
sessionTarget: "main",
|
|
||||||
createdAtMs: threeAM - DAY_MS,
|
|
||||||
updatedAtMs: threeAM - DAY_MS,
|
|
||||||
state: {
|
|
||||||
nextRunAtMs: threeAM, // Past-due by 1 second
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const state = createMockState([job], now);
|
const state = createMockState([job], now);
|
||||||
recomputeNextRuns(state);
|
recomputeNextRuns(state);
|
||||||
|
|||||||
@@ -192,6 +192,27 @@ function walkSchedulableJobs(
|
|||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function recomputeJobNextRunAtMs(params: { state: CronServiceState; job: CronJob; nowMs: number }) {
|
||||||
|
let changed = false;
|
||||||
|
try {
|
||||||
|
const newNext = computeJobNextRunAtMs(params.job, params.nowMs);
|
||||||
|
if (params.job.state.nextRunAtMs !== newNext) {
|
||||||
|
params.job.state.nextRunAtMs = newNext;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
// Clear schedule error count on successful computation.
|
||||||
|
if (params.job.state.scheduleErrorCount) {
|
||||||
|
params.job.state.scheduleErrorCount = undefined;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (recordScheduleComputeError({ state: params.state, job: params.job, err })) {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
export function recomputeNextRuns(state: CronServiceState): boolean {
|
export function recomputeNextRuns(state: CronServiceState): boolean {
|
||||||
return walkSchedulableJobs(state, ({ job, nowMs: now }) => {
|
return walkSchedulableJobs(state, ({ job, nowMs: now }) => {
|
||||||
let changed = false;
|
let changed = false;
|
||||||
@@ -201,21 +222,8 @@ export function recomputeNextRuns(state: CronServiceState): boolean {
|
|||||||
const nextRun = job.state.nextRunAtMs;
|
const nextRun = job.state.nextRunAtMs;
|
||||||
const isDueOrMissing = nextRun === undefined || now >= nextRun;
|
const isDueOrMissing = nextRun === undefined || now >= nextRun;
|
||||||
if (isDueOrMissing) {
|
if (isDueOrMissing) {
|
||||||
try {
|
if (recomputeJobNextRunAtMs({ state, job, nowMs: now })) {
|
||||||
const newNext = computeJobNextRunAtMs(job, now);
|
changed = true;
|
||||||
if (job.state.nextRunAtMs !== newNext) {
|
|
||||||
job.state.nextRunAtMs = newNext;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
// Clear schedule error count on successful computation.
|
|
||||||
if (job.state.scheduleErrorCount) {
|
|
||||||
job.state.scheduleErrorCount = undefined;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
if (recordScheduleComputeError({ state, job, err })) {
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return changed;
|
return changed;
|
||||||
@@ -236,21 +244,8 @@ export function recomputeNextRunsForMaintenance(state: CronServiceState): boolea
|
|||||||
// If a job was past-due but not found by findDueJobs, recomputing would
|
// If a job was past-due but not found by findDueJobs, recomputing would
|
||||||
// cause it to be silently skipped.
|
// cause it to be silently skipped.
|
||||||
if (job.state.nextRunAtMs === undefined) {
|
if (job.state.nextRunAtMs === undefined) {
|
||||||
try {
|
if (recomputeJobNextRunAtMs({ state, job, nowMs: now })) {
|
||||||
const newNext = computeJobNextRunAtMs(job, now);
|
changed = true;
|
||||||
if (job.state.nextRunAtMs !== newNext) {
|
|
||||||
job.state.nextRunAtMs = newNext;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
// Clear schedule error count on successful computation.
|
|
||||||
if (job.state.scheduleErrorCount) {
|
|
||||||
job.state.scheduleErrorCount = undefined;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
if (recordScheduleComputeError({ state, job, err })) {
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return changed;
|
return changed;
|
||||||
|
|||||||
Reference in New Issue
Block a user