sched: gate scheduled resumes on the shared execution slot, with drain tracking (B2)

This commit is contained in:
lda
2026-09-09 17:23:28 +07:00 Verified
parent fd2f85ef96
commit ada0239a1c
8 changed files with 459 additions and 15 deletions
+38
View File
@@ -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(