fix: reconcile live scheduled resume cancellation

This commit is contained in:
lda
2026-09-09 18:33:57 +07:00 Verified
parent 82dc5a80ae
commit 86a52adda5
3 changed files with 122 additions and 8 deletions
@@ -1408,6 +1408,87 @@ async def test_shutdown_timeout_fences_inflight_resume_and_restart_keeps_decisio
await service.stop()
async def test_caller_cancellation_reconciles_and_frees_capacity(
tmp_path: Path,
) -> None:
"""Caller cancellation fails one ambiguous resume without fencing capacity."""
_gate_open.clear()
root = tmp_path / "store"
server = build_local_static_workflow_server(root, extra_sources=_gate_sources())
await _seed(
server,
"ask_gate",
"ask_gate.default",
_interrupt_then_gate_plan("ask_gate"),
["submitted"],
)
await _seed(server, "const", "const.default", _constant_plan("const"), ["ok"])
store = FileScheduleStore(root)
store.create_schedule(_one_shot("asker", "ask_gate.default", datetime.now(UTC)))
service = _scheduler(server, capacity=1, auto_tick=False)
try:
await service.start()
async with asyncio.timeout(20):
while not _run_ids(root):
await service.poll_once(datetime.now(UTC))
await asyncio.sleep(0.02)
ask_id = _run_ids(root)[0]
await _wait_for(
lambda: FileRunStore(root).get_run(ask_id).status.value == "interrupted"
)
resume_task = asyncio.create_task(
server.api.resume_run(
run_id=ask_id, resume_payload={}, resume_outcome="submitted"
)
)
await _wait_for(lambda: ask_id in service._live_resumes)
await _wait_for(lambda: FileRunStore(root).is_executing(ask_id))
await _wait_for(
lambda: (
(attempt := FileRunStore(root).get_resume_attempt(ask_id)) is not None
and attempt.state == "ACTIVE"
)
)
resume_task.cancel()
with pytest.raises(asyncio.CancelledError):
await resume_task
assert service.running
assert not service._live_resumes
cancelled = FileRunStore(root).get_run(ask_id)
assert cancelled.status.value == "failed"
assert not FileRunStore(root).is_executing(ask_id)
attempt = FileRunStore(root).get_resume_attempt(ask_id)
assert attempt is not None and attempt.state == "ACTIVE"
assert len(_entries(root, "asker", "failed")) == 1
with pytest.raises(ValueError, match="ambiguous active resume attempt"):
await server.api.resume_run(
run_id=ask_id, resume_payload={}, resume_outcome="submitted"
)
assert _entries(root, "asker", "completed") == []
store.create_schedule(_one_shot("after", "const.default", datetime.now(UTC)))
async with asyncio.timeout(20):
while True:
await service.poll_once(datetime.now(UTC))
if len(_run_ids(root)) == 2:
after_id = [rid for rid in _run_ids(root) if rid != ask_id][0]
if FileRunStore(root).get_run(after_id).status.value == "completed":
break
await asyncio.sleep(0.02)
assert await _kinds(root, "asker") == [
"admitted",
"interrupted",
"failed",
]
assert await _kinds(root, "after") == ["admitted", "completed"]
finally:
_gate_open.set()
await service.stop()
async def test_scheduled_resume_requested_during_drain_rejected(
tmp_path: Path,
) -> None: