sched: gate scheduled resumes on the shared execution slot, with drain tracking (B2)
This commit is contained in:
@@ -66,10 +66,16 @@ class WorkflowRunApi:
|
||||
context: WorkflowOperationContext,
|
||||
*,
|
||||
resume_locks: AsyncKeyedLock | None = None,
|
||||
resume_slot_gate: Any | None = None,
|
||||
) -> None:
|
||||
self.context = context
|
||||
self.deployments = WorkflowDeploymentApi(context)
|
||||
self._resume_locks = resume_locks or AsyncKeyedLock()
|
||||
# Optional scheduler resume gate (installed by the opt-in server
|
||||
# scheduler composition): schedule-owned resumes acquire a shared
|
||||
# execution slot through it. Genuinely manual runs never consult
|
||||
# it, and a missing gate keeps the legacy path unchanged.
|
||||
self.resume_slot_gate = resume_slot_gate
|
||||
|
||||
def _run_store(self) -> RunStore:
|
||||
if self.context.run_store is None:
|
||||
@@ -189,6 +195,38 @@ class WorkflowRunApi:
|
||||
) -> RunResult:
|
||||
trace_values = _trace_range_values(trace_range)
|
||||
store = self._run_store()
|
||||
# Shared execution slot for schedule-owned resumes: the resume
|
||||
# gate holds the scheduler's own capacity accounting (no second
|
||||
# semaphore) behind the durable executing mark. Acquisition runs
|
||||
# BEFORE the ACTIVE attempt mark, so a busy rejection leaves no
|
||||
# fake ACTIVE attempt for work that never dispatched. Manual runs
|
||||
# (no schedule admission, or no live scheduler) skip the gate.
|
||||
gate = self.resume_slot_gate
|
||||
slot = gate.acquire(run_id) if gate is not None else None
|
||||
slot_held = slot is not None
|
||||
try:
|
||||
return await self._resume_scheduled_or_manual(
|
||||
run_id=run_id,
|
||||
resume_payload=resume_payload,
|
||||
resume_outcome=resume_outcome,
|
||||
trace_range=trace_range,
|
||||
trace_values=trace_values,
|
||||
store=store,
|
||||
)
|
||||
finally:
|
||||
if slot_held and gate is not None:
|
||||
gate.release(run_id)
|
||||
|
||||
async def _resume_scheduled_or_manual(
|
||||
self,
|
||||
*,
|
||||
run_id: str,
|
||||
resume_payload: dict[str, Any],
|
||||
resume_outcome: str,
|
||||
trace_range: TraceRangeLike | None,
|
||||
trace_values: tuple[int, int] | None,
|
||||
store: RunStore,
|
||||
) -> RunResult:
|
||||
pre_attempt = store.get_resume_attempt(run_id)
|
||||
if pre_attempt is not None and pre_attempt.state == "ACTIVE":
|
||||
raise ValueError(
|
||||
|
||||
Reference in New Issue
Block a user