fix: finish cancelled resume cleanup
This commit is contained in:
@@ -36,6 +36,7 @@ from wf_scheduling.lifecycle import (
|
||||
from wf_scheduling.models import Schedule
|
||||
from wf_scheduling.ownership import SchedulerOwnership, SecondOwnerError
|
||||
from wf_scheduling.poll import Scheduler
|
||||
from wf_scheduling.resume_gate import SchedulerResumeGate
|
||||
from wf_scheduling.store import FileScheduleStore
|
||||
|
||||
|
||||
@@ -405,6 +406,57 @@ asyncio.run(main())
|
||||
assert "registration-stop-complete" in output
|
||||
|
||||
|
||||
async def test_cancelled_resume_release_waits_for_lock_cleanup(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""Cancellation cannot skip scheduled-resume slot cleanup."""
|
||||
intended = ts(2026, 9, 8, 12, 0)
|
||||
service = _service(tmp_path, ScriptedRuntime("interrupt"))
|
||||
lock_held = threading.Event()
|
||||
release_lock = threading.Event()
|
||||
holder: threading.Thread | None = None
|
||||
release_task: asyncio.Task[Any] | None = None
|
||||
try:
|
||||
await service.start()
|
||||
service.schedule_store.create_schedule(_sched_model("a", intended))
|
||||
await service.poll_once(intended + timedelta(seconds=1))
|
||||
run_id = _only_run_id(service.run_store)
|
||||
await _wait_for(lambda: service.live_executions == 0)
|
||||
|
||||
gate = SchedulerResumeGate(service)
|
||||
assert await gate.acquire(run_id, owner_task=asyncio.current_task())
|
||||
|
||||
def hold_service_lock() -> None:
|
||||
service._lock.acquire()
|
||||
lock_held.set()
|
||||
release_lock.wait(5)
|
||||
service._lock.release()
|
||||
|
||||
holder = threading.Thread(target=hold_service_lock, daemon=True)
|
||||
holder.start()
|
||||
assert await asyncio.to_thread(lock_held.wait, 5)
|
||||
|
||||
release_task = asyncio.create_task(gate.release(run_id))
|
||||
await asyncio.sleep(0.05)
|
||||
assert not release_task.done()
|
||||
release_task.cancel()
|
||||
release_lock.set()
|
||||
with pytest.raises(asyncio.CancelledError):
|
||||
await asyncio.wait_for(release_task, 5)
|
||||
|
||||
assert not service.run_store.is_executing(run_id)
|
||||
assert run_id not in service._live_resumes
|
||||
assert run_id not in service._resume_tasks
|
||||
finally:
|
||||
release_lock.set()
|
||||
if release_task is not None and not release_task.done():
|
||||
release_task.cancel()
|
||||
await asyncio.gather(release_task, return_exceptions=True)
|
||||
if holder is not None:
|
||||
holder.join(timeout=5)
|
||||
await service.stop()
|
||||
|
||||
|
||||
async def test_scheduled_interrupt_stays_resumable(tmp_path: Path) -> None:
|
||||
intended = ts(2026, 9, 8, 12, 0)
|
||||
service = _service(tmp_path, ScriptedRuntime("interrupt"))
|
||||
|
||||
Reference in New Issue
Block a user