fix(cron): skip non-object entries in jobs.json instead of halting every tick
A null, string or number in the canonical {"jobs": [...]} list reached
every reader: the due scan raised AttributeError on each tick, so no job
fired, and list/resolve crashed too. Drop the junk with a warning and
self-heal the file, as the id-keyed map flatten already does.
(cherry picked from commit 2e2438d160967392184b2193cfc2e2ca124c2b7a)
This commit is contained in:
committed by
kshitij
parent
a0fe806c46
commit
695766e3f9
@@ -1374,6 +1374,15 @@ def load_jobs() -> List[Dict[str, Any]]:
|
||||
else:
|
||||
raise RuntimeError(
|
||||
f"Cron database corrupted: expected {{'jobs': [...]}}, got {type(data).__name__}")
|
||||
if isinstance(jobs, list) and not all(isinstance(j, dict) for j in jobs):
|
||||
# Every reader and the due scan index records as dicts: one junk entry would crash the
|
||||
# whole tick and freeze every healthy sibling job, so skip it like the id-keyed map does.
|
||||
junk = [j for j in jobs if not isinstance(j, dict)]
|
||||
logger.warning(
|
||||
"Skipping %d non-object entr%s in jobs.json: %s",
|
||||
len(junk), "y" if len(junk) == 1 else "ies", ", ".join(map(repr, junk[:5])))
|
||||
jobs = [j for j in jobs if isinstance(j, dict)]
|
||||
repair = repair or "non-object entries dropped"
|
||||
if jobs and repair:
|
||||
save_jobs(jobs)
|
||||
logger.warning("Auto-repaired jobs.json (%s)", repair)
|
||||
|
||||
@@ -1718,6 +1718,23 @@ class TestJobsJsonIdKeyedMap:
|
||||
assert isinstance(on_disk["jobs"], list)
|
||||
assert [j["id"] for j in on_disk["jobs"]] == ["goodjob1"]
|
||||
|
||||
def test_non_dict_list_entries_do_not_stop_healthy_jobs_firing(self, tmp_cron_dir):
|
||||
"""A junk entry in the canonical list shape must not abort the due scan for its
|
||||
healthy siblings (it used to raise on every tick, so no job fired)."""
|
||||
import json
|
||||
from cron.jobs import JOBS_FILE
|
||||
|
||||
job = create_job(prompt="keep me", schedule="every 1h", name="survivor")
|
||||
payload = json.loads(JOBS_FILE.read_text(encoding="utf-8"))
|
||||
payload["jobs"][0]["next_run_at"] = (_hermes_now() - timedelta(seconds=5)).isoformat()
|
||||
payload["jobs"] += [None, "i am not a job", 42]
|
||||
JOBS_FILE.write_text(json.dumps(payload), encoding="utf-8")
|
||||
|
||||
assert [j["id"] for j in get_due_jobs()] == [job["id"]]
|
||||
assert [j["id"] for j in list_jobs(include_disabled=True)] == [job["id"]]
|
||||
on_disk = json.loads(JOBS_FILE.read_text(encoding="utf-8"))
|
||||
assert [j["id"] for j in on_disk["jobs"]] == [job["id"]]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user