Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
"""Misfire-backstop grace gate for one-shot cron jobs (#93526).
|
||||
|
||||
``fire_overdue_jobs`` in ``cron/scheduler_provider.py`` is the hosted-provider
|
||||
misfire catch-up: any runnable job whose ``next_run_at`` is older than
|
||||
``cron.misfire_grace_minutes`` gets fired locally. Before the fix it applied
|
||||
no one-shot grace check, so a stored past-due one-shot bypassed
|
||||
ONESHOT_GRACE_SECONDS and fired arbitrarily late after downtime — the exact
|
||||
"will never fire" contract violation the due-scan gate (#89571) closes on the
|
||||
built-in scheduler path.
|
||||
|
||||
Pins:
|
||||
- one-shot beyond ONESHOT_GRACE_SECONDS -> backstop skips it (no claim)
|
||||
- recurring job equally overdue -> backstop still fires it
|
||||
"""
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from cron.jobs import ONESHOT_GRACE_SECONDS, _hermes_now
|
||||
from cron.scheduler_provider import fire_overdue_jobs
|
||||
|
||||
|
||||
class _RecordingProvider:
|
||||
"""Non-InProcess provider double that records claim attempts."""
|
||||
|
||||
def __init__(self):
|
||||
self.claimed = []
|
||||
|
||||
def claim_fire(self, job_id):
|
||||
self.claimed.append(job_id)
|
||||
return None # decline the claim so no thread is spawned
|
||||
|
||||
def fire_claimed(self, *a, **k): # pragma: no cover - never reached
|
||||
raise AssertionError("fire_claimed must not run when claim declined")
|
||||
|
||||
|
||||
def _job(jid, kind, run_at_dt):
|
||||
schedule = {"kind": kind}
|
||||
if kind == "once":
|
||||
schedule["run_at"] = run_at_dt.isoformat()
|
||||
else:
|
||||
schedule["expr"] = "*/5 * * * *"
|
||||
return {
|
||||
"id": jid,
|
||||
"name": jid,
|
||||
"prompt": "x",
|
||||
"schedule": schedule,
|
||||
"next_run_at": run_at_dt.isoformat(),
|
||||
"last_run_at": None,
|
||||
"enabled": True,
|
||||
"state": "scheduled",
|
||||
"repeat": {"times": 1 if kind == "once" else None, "completed": 0},
|
||||
"deliver": "local",
|
||||
}
|
||||
|
||||
|
||||
def _run_backstop(monkeypatch, jobs):
|
||||
provider = _RecordingProvider()
|
||||
monkeypatch.setattr("cron.jobs.load_jobs", lambda: jobs)
|
||||
monkeypatch.setattr("cron.jobs.is_job_runnable", lambda j: True)
|
||||
monkeypatch.setattr(
|
||||
"cron.scheduler_provider._misfire_grace_minutes", lambda: 5.0
|
||||
)
|
||||
fired = fire_overdue_jobs(provider)
|
||||
return provider, fired
|
||||
|
||||
|
||||
class TestMisfireBackstopOneShotGrace:
|
||||
def test_stale_oneshot_beyond_grace_is_not_fired(self, monkeypatch):
|
||||
now = _hermes_now()
|
||||
stale = _job("stale-once", "once", now - timedelta(hours=2))
|
||||
provider, fired = _run_backstop(monkeypatch, [stale])
|
||||
assert fired == 0
|
||||
assert provider.claimed == []
|
||||
|
||||
def test_overdue_recurring_job_still_fires(self, monkeypatch):
|
||||
now = _hermes_now()
|
||||
overdue = _job("overdue-cron", "cron", now - timedelta(hours=2))
|
||||
provider, fired = _run_backstop(monkeypatch, [overdue])
|
||||
assert provider.claimed == ["overdue-cron"]
|
||||
|
||||
def test_oneshot_within_oneshot_grace_but_past_misfire_grace(
|
||||
self, monkeypatch
|
||||
):
|
||||
# misfire grace patched to ~0 so a 60s-late one-shot is "overdue" for
|
||||
# the backstop but still inside ONESHOT_GRACE_SECONDS -> must fire.
|
||||
assert ONESHOT_GRACE_SECONDS > 60
|
||||
now = _hermes_now()
|
||||
recent = _job("recent-once", "once", now - timedelta(seconds=60))
|
||||
provider = _RecordingProvider()
|
||||
monkeypatch.setattr("cron.jobs.load_jobs", lambda: [recent])
|
||||
monkeypatch.setattr("cron.jobs.is_job_runnable", lambda j: True)
|
||||
monkeypatch.setattr(
|
||||
"cron.scheduler_provider._misfire_grace_minutes", lambda: 0.5
|
||||
)
|
||||
fire_overdue_jobs(provider)
|
||||
assert provider.claimed == ["recent-once"]
|
||||
Reference in New Issue
Block a user