mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:30:42 +00:00
@@ -4,7 +4,9 @@ import {
|
||||
clearCronScheduleCacheForTest,
|
||||
computeNextRunAtMs,
|
||||
computePreviousRunAtMs,
|
||||
getCronScheduleCacheMaxForTest,
|
||||
getCronScheduleCacheSizeForTest,
|
||||
hasCronInCacheForTest,
|
||||
} from "./schedule.js";
|
||||
|
||||
describe("cron schedule", () => {
|
||||
@@ -144,6 +146,39 @@ describe("cron schedule", () => {
|
||||
expect(getCronScheduleCacheSizeForTest()).toBe(2);
|
||||
});
|
||||
|
||||
it("promotes accessed entries to avoid premature LRU eviction", () => {
|
||||
const nowMs = Date.parse("2026-03-01T00:00:00.000Z");
|
||||
const cacheMax = getCronScheduleCacheMaxForTest();
|
||||
|
||||
// Fill cache to capacity with unique expressions.
|
||||
// i=0 → "0 0 * * *", i=1 → "1 0 * * *", ..., i=511 → "31 8 * * *"
|
||||
for (let i = 0; i < cacheMax; i++) {
|
||||
computeNextRunAtMs(
|
||||
{ kind: "cron", expr: `${i % 60} ${Math.floor(i / 60)} * * *`, tz: "UTC" },
|
||||
nowMs,
|
||||
);
|
||||
}
|
||||
expect(getCronScheduleCacheSizeForTest()).toBe(cacheMax);
|
||||
|
||||
// Entry #0 ("0 0 * * *") is the oldest by insertion order.
|
||||
// Access it so LRU promotes it (delete + re-insert at end of Map).
|
||||
computeNextRunAtMs({ kind: "cron", expr: "0 0 * * *", tz: "UTC" }, nowMs);
|
||||
|
||||
// Entry #1 ("1 0 * * *") is now the least-recently-used.
|
||||
// Insert a new entry to trigger one eviction.
|
||||
computeNextRunAtMs({ kind: "cron", expr: "0 0 1 1 *", tz: "UTC" }, nowMs);
|
||||
expect(getCronScheduleCacheSizeForTest()).toBe(cacheMax);
|
||||
|
||||
// Under LRU: entry #0 survived (was promoted), entry #1 was evicted.
|
||||
// Under FIFO: entry #0 would be evicted instead — this assertion would fail.
|
||||
expect(hasCronInCacheForTest("0 0 * * *", "UTC")).toBe(true);
|
||||
expect(hasCronInCacheForTest("1 0 * * *", "UTC")).toBe(false);
|
||||
|
||||
// The new entry and a non-evicted middle entry should both be present.
|
||||
expect(hasCronInCacheForTest("0 0 1 1 *", "UTC")).toBe(true);
|
||||
expect(hasCronInCacheForTest("2 0 * * *", "UTC")).toBe(true);
|
||||
});
|
||||
|
||||
describe("cron with specific seconds (6-field pattern)", () => {
|
||||
// Pattern: fire at exactly second 0 of minute 0 of hour 12 every day
|
||||
const dailyNoon = { kind: "cron" as const, expr: "0 0 12 * * *", tz: "UTC" };
|
||||
|
||||
@@ -18,6 +18,9 @@ function resolveCachedCron(expr: string, timezone: string): Cron {
|
||||
const key = `${timezone}\u0000${expr}`;
|
||||
const cached = cronEvalCache.get(key);
|
||||
if (cached) {
|
||||
// Move to end of Map iteration order for LRU eviction
|
||||
cronEvalCache.delete(key);
|
||||
cronEvalCache.set(key, cached);
|
||||
return cached;
|
||||
}
|
||||
if (cronEvalCache.size >= CRON_EVAL_CACHE_MAX) {
|
||||
@@ -169,3 +172,11 @@ export function clearCronScheduleCacheForTest(): void {
|
||||
export function getCronScheduleCacheSizeForTest(): number {
|
||||
return cronEvalCache.size;
|
||||
}
|
||||
|
||||
export function getCronScheduleCacheMaxForTest(): number {
|
||||
return CRON_EVAL_CACHE_MAX;
|
||||
}
|
||||
|
||||
export function hasCronInCacheForTest(expr: string, tz: string): boolean {
|
||||
return cronEvalCache.has(`${tz}\u0000${expr}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user