sched: require proven ownership at poll/admin/recovery entries (F5)
This commit is contained in:
@@ -27,6 +27,7 @@ from wf_scheduling.calendar import (
|
||||
from wf_scheduling.dispatch import RunDispatcher, StillRunning
|
||||
from wf_scheduling.models import OccurrenceRecord, PendingCandidate
|
||||
from wf_scheduling.occurrences import occurrence_id
|
||||
from wf_scheduling.ownership import SchedulerOwnership, SecondOwnerError
|
||||
from wf_scheduling.prepare import InvocationPreparer, PreparationRejected
|
||||
|
||||
UTC = timezone.utc
|
||||
@@ -84,6 +85,7 @@ class Scheduler:
|
||||
capacity: int,
|
||||
preparer: InvocationPreparer,
|
||||
dispatcher: RunDispatcher,
|
||||
ownership: SchedulerOwnership,
|
||||
) -> None:
|
||||
self.schedule_store = schedule_store
|
||||
self.run_store = run_store
|
||||
@@ -91,8 +93,21 @@ class Scheduler:
|
||||
self.capacity = capacity
|
||||
self.preparer = preparer
|
||||
self.dispatcher = dispatcher
|
||||
self.ownership = ownership
|
||||
self._poll_cursor = 0
|
||||
|
||||
def _require_ownership(self) -> None:
|
||||
"""Reject schedule mutation/dispatch without proven live ownership.
|
||||
|
||||
Runs before any store write or dispatcher side effect: without a
|
||||
held lock this process cannot prove exclusive ownership, so polling
|
||||
or administering schedules would risk double admission.
|
||||
"""
|
||||
if self.ownership is None or not self.ownership.held:
|
||||
raise SecondOwnerError(
|
||||
"scheduler ownership is required before polling or mutating schedules"
|
||||
)
|
||||
|
||||
# -- helpers ------------------------------------------------------
|
||||
@staticmethod
|
||||
def _status_value(run: Any) -> Any:
|
||||
@@ -342,6 +357,7 @@ class Scheduler:
|
||||
|
||||
# -- polling --------------------------------------------------------
|
||||
def poll(self, now: datetime) -> dict[str, str]:
|
||||
self._require_ownership()
|
||||
self._dispatch_pending(now)
|
||||
schedules = self.schedule_store.list_schedules(include_deleted=True)
|
||||
ids = sorted(item.id for item in schedules)
|
||||
@@ -616,6 +632,7 @@ class Scheduler:
|
||||
# -- administration ---------------------------------------------------
|
||||
def resume_schedule(self, sid: str, now: datetime) -> None:
|
||||
"""Unpause: resume selects the next future occurrence."""
|
||||
self._require_ownership()
|
||||
sched = self.schedule_store.get_schedule(sid)
|
||||
sched.paused = False
|
||||
self.schedule_store.save_schedule(sched)
|
||||
@@ -625,6 +642,7 @@ class Scheduler:
|
||||
|
||||
def edit_schedule(self, sid: str, now: datetime) -> None:
|
||||
"""Definition edit: new revision, discard old candidates, no backfill."""
|
||||
self._require_ownership()
|
||||
sched = self.schedule_store.get_schedule(sid)
|
||||
sched.revision += 1
|
||||
sched.updated_at = now
|
||||
|
||||
Reference in New Issue
Block a user