sched: bind ownership to store composition; stable recovery failure; schema-checked prepare; guarded settle (R4 wave 2)
This commit is contained in:
@@ -139,13 +139,20 @@ class Scheduler:
|
||||
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
|
||||
Runs before any store write or dispatcher side effect: the held
|
||||
lock must cover the actual schedule and run store composition, not
|
||||
merely be held on some unrelated directory. Without covering
|
||||
ownership 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:
|
||||
ownership = self.ownership
|
||||
if ownership is None or not ownership.covers(
|
||||
getattr(self.schedule_store, "root", None),
|
||||
getattr(self.run_store, "root", None),
|
||||
):
|
||||
raise SecondOwnerError(
|
||||
"scheduler ownership is required before polling or mutating schedules"
|
||||
"scheduler ownership must cover the schedule and run stores "
|
||||
"before polling or mutating schedules"
|
||||
)
|
||||
|
||||
# -- helpers ------------------------------------------------------
|
||||
@@ -431,6 +438,12 @@ class Scheduler:
|
||||
:class:`wf_core.RunState`. Persists through the shared lifecycle
|
||||
boundary, clears the executing mark, and records terminal history.
|
||||
Never re-invokes the dispatcher.
|
||||
|
||||
An admitted status alone is not enough: pending, never-dispatched
|
||||
work is also admitted. Settlement requires the durable executing
|
||||
transition and refuses contradictory pending/executing state (a
|
||||
crash between the transition writes owns that run now, not this
|
||||
caller); it also refuses runs that already stopped.
|
||||
"""
|
||||
self._require_ownership()
|
||||
from wf_api.run_lifecycle import persist_stopped_run
|
||||
@@ -448,6 +461,12 @@ class Scheduler:
|
||||
raise BlockedSchedule(f"settle missing run view: {run_id!r}") from exc
|
||||
if self._status_value(record) != "admitted":
|
||||
raise BlockedSchedule(f"settle non-admitted run: {run_id!r}")
|
||||
if self.run_store.is_pending_dispatch(run_id):
|
||||
raise BlockedSchedule(
|
||||
f"settle contradictory pending/executing run: {run_id!r}"
|
||||
)
|
||||
if not self.run_store.is_executing(run_id):
|
||||
raise BlockedSchedule(f"settle without executing transition: {run_id!r}")
|
||||
stopped = persist_stopped_run(
|
||||
store=self.run_store,
|
||||
environment=admission.environment,
|
||||
|
||||
Reference in New Issue
Block a user