fix: close scheduler persistence and capacity gaps

This commit is contained in:
lda
2026-09-09 19:13:17 +07:00 Verified
parent 39993c9d5e
commit 787a3c433f
7 changed files with 296 additions and 13 deletions
+179
View File
@@ -392,6 +392,185 @@ def test_manual_and_other_schedule_runs_are_overlap_independent(
sched.ownership.release()
def test_manual_admission_does_not_consume_scheduler_capacity(tmp_path: Path) -> None:
"""Manual runs bypass scheduler capacity; scheduled work still dispatches."""
from wf_api.run_lifecycle import materialize_admitted_view, persist_admission
sched, store, runs, sources = _harness(tmp_path, capacity=1, script={"*": "hang"})
t0 = ts(2026, 9, 8, 12, 0)
manual = persist_admission(
store=runs,
run_id=runs.allocate_run_id(),
environment=fixture_environment(object()),
resolved_input={},
max_steps=None,
)
materialize_admitted_view(store=runs, admission=manual)
assert sched._task_load() == 0
_add(sched, store, sources, "a", OneShotSource(t0), t0 - timedelta(hours=1))
assert sched.poll(t0)["a"].startswith("admit:run-")
sched.ownership.release()
def test_existing_admission_rebuilds_missing_view_and_pending_marker(
tmp_path: Path,
) -> None:
"""A history tear after admission remains dispatchable on the next poll."""
class FailHistoryOnce(FileScheduleStore):
def __init__(self, root: Path) -> None:
super().__init__(root)
self.armed = True
def append_history(self, record: Any) -> None:
if self.armed:
self.armed = False
raise OSError("injected history failure")
super().append_history(record)
sched_store = FailHistoryOnce(tmp_path / "sched")
run_store = FileRunStore(tmp_path / "runs")
sources: dict[str, Any] = {}
t0 = ts(2026, 9, 8, 12, 0)
sched = Scheduler(
schedule_store=sched_store,
run_store=run_store,
sources=sources,
capacity=1,
preparer=SchedulePreparer(
DictDeployments({"dep-1": {"rev": 1, "required": []}}),
fixture_environment,
),
dispatcher=ScriptedDispatcher({"*": "complete"}),
ownership=SchedulerOwnership(tmp_path, owner="test").acquire(),
)
try:
_add(
sched, sched_store, sources, "a", OneShotSource(t0), t0 - timedelta(hours=1)
)
try:
sched.poll(t0)
raise AssertionError("history failure must propagate")
except OSError as exc:
assert "injected history failure" in str(exc)
[admission] = run_store.list_admissions()
assert run_store.list_runs() == []
assert not run_store.is_pending_dispatch(admission.id)
result = sched.poll(t0 + timedelta(seconds=1))
assert result["a"] == f"admit:{admission.id}"
assert run_store.get_run(admission.id).status.value == "admitted"
assert run_store.is_pending_dispatch(admission.id)
sched.poll(t0 + timedelta(seconds=2))
assert run_store.get_run(admission.id).status.value == "completed"
assert not run_store.is_pending_dispatch(admission.id)
finally:
sched.ownership.release()
def test_oneshot_terminal_bookkeeping_is_idempotent_after_torn_write(
tmp_path: Path,
) -> None:
"""A torn exhausted transition does not duplicate history or retain a candidate."""
class FailScheduleOnce(FileScheduleStore):
def __init__(self, root: Path) -> None:
super().__init__(root)
self.armed = False
def save_schedule(self, schedule: Any) -> None:
if self.armed:
self.armed = False
raise OSError("injected schedule failure")
super().save_schedule(schedule)
sched_store = FailScheduleOnce(tmp_path / "sched")
run_store = FileRunStore(tmp_path / "runs")
sources: dict[str, Any] = {}
t0 = ts(2026, 9, 8, 12, 0)
sched = Scheduler(
schedule_store=sched_store,
run_store=run_store,
sources=sources,
capacity=1,
preparer=SchedulePreparer(
DictDeployments({"dep-1": {"rev": 1, "required": []}}),
fixture_environment,
),
dispatcher=ScriptedDispatcher({"*": "complete"}),
ownership=SchedulerOwnership(tmp_path, owner="test").acquire(),
)
try:
_add(
sched, sched_store, sources, "a", OneShotSource(t0), t0 - timedelta(hours=1)
)
from wf_scheduling.models import PendingCandidate
sched_store.save_candidate(
PendingCandidate(schedule_id="a", intended_at=t0, revision=1),
schedule_id="a",
)
sched_store.armed = True
try:
sched.poll(t0 + timedelta(minutes=5))
raise AssertionError("schedule failure must propagate")
except OSError as exc:
assert "injected schedule failure" in str(exc)
assert (
len([r for r in _history(sched_store, "a") if r["kind"] == "exhausted"])
== 1
)
assert sched_store.get_candidate("a") is not None
assert sched.poll(t0 + timedelta(minutes=6))["a"] == "exhausted"
assert (
len([r for r in _history(sched_store, "a") if r["kind"] == "exhausted"])
== 1
)
assert sched_store.get_candidate("a") is None
finally:
sched.ownership.release()
def test_corrupt_unattributed_view_isolated_from_healthy_schedules(
tmp_path: Path,
) -> None:
"""A view without an admission fails closed without blocking siblings."""
from wf_artifacts import ResumeReadiness, WorkflowRunRecord
from wf_artifacts.runs.models import StoredRunStatus
sched, store, runs, sources = _harness(tmp_path, script={"*": "complete"})
t0 = ts(2026, 9, 8, 12, 0)
_add(sched, store, sources, "a", OneShotSource(t0), t0 - timedelta(hours=1))
_add(sched, store, sources, "b", OneShotSource(t0), t0 - timedelta(hours=1))
corrupt_id = runs.allocate_run_id()
runs.save_run(
WorkflowRunRecord(
id=corrupt_id,
status=StoredRunStatus.ADMITTED,
resume_readiness=ResumeReadiness.NOT_APPLICABLE,
environment=fixture_environment(object()),
latest_checkpoint_id=None,
created_at=t0,
updated_at=t0,
)
)
results = sched.poll(t0)
assert results["a"].startswith("admit:run-")
assert results["b"].startswith("admit:run-")
assert runs.get_run(corrupt_id).status.value == "failed"
assert all(
runs.get_run(run.id).status.value == "completed"
for run in runs.list_runs()
if run.id != corrupt_id
)
sched.ownership.release()
def test_record_resumed_stopped_result_is_idempotent_and_attributed(
tmp_path: Path,
) -> None: