mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 08:31:24 +00:00
fix(cron): fix timeout, add timestamp validation, enable file sync
Fixes #7667 Task 1: Fix cron operation timeouts - Increase default gateway tool timeout from 10s to 30s - Increase cron-specific tool timeout to 60s - Increase CLI default timeout from 10s to 30s - Prevents timeouts when gateway is busy with long-running jobs Task 2: Add timestamp validation - New validateScheduleTimestamp() function in validate-timestamp.ts - Rejects atMs timestamps more than 1 minute in the past - Rejects atMs timestamps more than 10 years in the future - Applied to both cron.add and cron.update operations - Provides helpful error messages with current time and offset Task 3: Enable file sync for manual edits - Track file modification time (storeFileMtimeMs) in CronServiceState - Check file mtime in ensureLoaded() and reload if changed - Recompute next runs after reload to maintain accuracy - Update mtime after persist() to prevent reload loop - Dashboard now picks up manual edits to ~/.openclaw/cron/jobs.json
This commit is contained in:
committed by
Peter Steinberger
parent
a749db9820
commit
3a03e38378
@@ -48,6 +48,8 @@ export type CronServiceState = {
|
||||
running: boolean;
|
||||
op: Promise<unknown>;
|
||||
warnedDisabled: boolean;
|
||||
storeLoadedAtMs: number | null;
|
||||
storeFileMtimeMs: number | null;
|
||||
};
|
||||
|
||||
export function createCronServiceState(deps: CronServiceDeps): CronServiceState {
|
||||
@@ -58,6 +60,8 @@ export function createCronServiceState(deps: CronServiceDeps): CronServiceState
|
||||
running: false,
|
||||
op: Promise.resolve(),
|
||||
warnedDisabled: false,
|
||||
storeLoadedAtMs: null,
|
||||
storeFileMtimeMs: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,37 @@
|
||||
import fs from "node:fs";
|
||||
import type { CronJob } from "../types.js";
|
||||
import type { CronServiceState } from "./state.js";
|
||||
import { migrateLegacyCronPayload } from "../payload-migration.js";
|
||||
import { loadCronStore, saveCronStore } from "../store.js";
|
||||
import { recomputeNextRuns } from "./jobs.js";
|
||||
import { inferLegacyName, normalizeOptionalText } from "./normalize.js";
|
||||
|
||||
const storeCache = new Map<string, { version: 1; jobs: CronJob[] }>();
|
||||
async function getFileMtimeMs(path: string): Promise<number | null> {
|
||||
try {
|
||||
const stats = await fs.promises.stat(path);
|
||||
return stats.mtimeMs;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function ensureLoaded(state: CronServiceState) {
|
||||
if (state.store) {
|
||||
return;
|
||||
}
|
||||
const cached = storeCache.get(state.deps.storePath);
|
||||
if (cached) {
|
||||
state.store = cached;
|
||||
const fileMtimeMs = await getFileMtimeMs(state.deps.storePath);
|
||||
|
||||
// Check if we need to reload:
|
||||
// - No store loaded yet
|
||||
// - File modification time has changed
|
||||
// - File was modified after we last loaded (external edit)
|
||||
const needsReload =
|
||||
!state.store ||
|
||||
(fileMtimeMs !== null &&
|
||||
state.storeFileMtimeMs !== null &&
|
||||
fileMtimeMs > state.storeFileMtimeMs);
|
||||
|
||||
if (!needsReload) {
|
||||
return;
|
||||
}
|
||||
|
||||
const loaded = await loadCronStore(state.deps.storePath);
|
||||
const jobs = (loaded.jobs ?? []) as unknown as Array<Record<string, unknown>>;
|
||||
let mutated = false;
|
||||
@@ -44,7 +61,12 @@ export async function ensureLoaded(state: CronServiceState) {
|
||||
}
|
||||
}
|
||||
state.store = { version: 1, jobs: jobs as unknown as CronJob[] };
|
||||
storeCache.set(state.deps.storePath, state.store);
|
||||
state.storeLoadedAtMs = state.deps.nowMs();
|
||||
state.storeFileMtimeMs = fileMtimeMs;
|
||||
|
||||
// Recompute next runs after loading to ensure accuracy
|
||||
recomputeNextRuns(state);
|
||||
|
||||
if (mutated) {
|
||||
await persist(state);
|
||||
}
|
||||
@@ -69,4 +91,6 @@ export async function persist(state: CronServiceState) {
|
||||
return;
|
||||
}
|
||||
await saveCronStore(state.deps.storePath, state.store);
|
||||
// Update file mtime after save to prevent immediate reload
|
||||
state.storeFileMtimeMs = await getFileMtimeMs(state.deps.storePath);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user