Ilya Yakavets
12/18/2025, 5:07 PMMarvin
12/18/2025, 5:07 PMMarvin
12/18/2025, 5:12 PMcollision_strategy=CANCEL_NEW on the deployment (new runs are cancelled instead of queued), but there isn’t a “max lateness” or “catch‑up=False” toggle that disqualifies waiting runs.
- If it “should run when a slot frees,” why would it never start? Most commonly: a run is continuously holding the slot (RUNNING/PENDING), queue/pool concurrency or priority is starving B, or B is waiting on a different limit key than you expect. See checks below.
What “Awaiting concurrency slot” means
- It’s a Scheduled state applied when a run can’t acquire a concurrency slot (deployment concurrency, global concurrency, or tag concurrency). Prefect reschedules the run to retry after a short delay (default ~30s, configurable).
- It may also be marked Late after its scheduled time passes, but “Late” is informational/for SLAs — it does not block execution.
Key settings and where they apply
- Retry delay when a slot isn’t available:
- Deployment concurrency retry cadence: PREFECT_SERVER_DEPLOYMENTS_CONCURRENCY_SLOT_WAIT_SECONDS (default ~30s)
- Lease/hold behavior:
- Deployment concurrency lease duration: PREFECT_SERVER_CONCURRENCY_INITIAL_DEPLOYMENT_LEASE_DURATION (default ~300s)
- Grace period on deployments: concurrency_options.grace_period_seconds (default 300s). If infra spin‑up is slow, increase this so the slot isn’t released too early; if it’s too long and runs get stuck Pending, it can “feel” like starvation.
- Late run marking (observability only):
- PREFECT_SERVER_SERVICES_LATE_RUNS_AFTER_SECONDS (default 15s)
Common reasons a run stays “Awaiting concurrency slot”
- A RUNNING or PENDING run is continuously holding the slot
- For global concurrency: another run (likely from deployment A) has the global limit lease most of the time. If A is generating backlog, it can starve B.
- For deployment concurrency: a stuck RUNNING or long‑Pending run is holding the deployment’s slot.
- Queue/pool concurrency or priority is starving B
- Work pool/queue concurrency limits or queue priority can cause workers to keep pulling A’s runs first.
- Workers aren’t pulling B’s runs
- No worker attached to B’s pool/queue, or the worker is unhealthy.
- Different concurrency keys than intended
- If A and B don’t actually share the same global limit key, B may be waiting on a separate (possibly stuck) limit.
- Collision strategy not enqueueing
- If collision_strategy=CANCEL_NEW, new runs are cancelled instead of waiting (you’d see Cancelled, not Awaiting; mentioning it for completeness).
What to check (UI/API)
- Global concurrency usage
- In the UI: Concurrency page (Global Concurrency Limits). Verify the limit name/key both deployments use, current in‑use slots, which flow run(s) hold them, and lease expiration.
- Docs: Global concurrency limits
- Deployment‑level concurrency settings
- On each deployment: concurrency_limit, concurrency_options.collision_strategy, grace_period_seconds. Confirm they’re as expected and that you’re not also limiting at the deployment level in a way that causes double‑gating.
- Docs: Deployments (concurrency section)
- Active runs holding slots
- Filter Flow Runs for the deployments and look for RUNNING/PENDING runs, then open a run and check the Concurrency/resources tab to see what it’s holding.
- API example to list RUNNING/PENDING for a deployment:
```Marvin
12/18/2025, 5:12 PMcollision_strategy=CANCEL_NEW on the deployments.
- Tune grace_period_seconds to match infra startup so slots aren’t held too long by Pending runs.
- If you suspect a stuck lease/run, cancel that run or stop/restart the worker; leases that aren’t renewed will expire, releasing the slot.
References
- States: States
- Schedules: Schedules
- SLAs: SLAs
- Deployments (concurrency): Deployments
- Global concurrency limits: Global concurrency limits
- Work pools/queues and queue priority: Work pools
- Settings reference (late run window, concurrency wait/lease settings): Settings reference
If you can share:
- whether A and B definitely share the same global limit key,
- their queue priorities and work pool limits,
- and a screenshot of the Concurrency page showing who holds the slot,
I can help pinpoint exactly why B is getting starved and suggest the minimal change to fix it.Ilya Yakavets
12/18/2025, 8:46 PMMarvin
12/18/2025, 8:46 PMMarvin
12/18/2025, 8:49 PMcollision_strategy=CANCEL_NEW so new runs are cancelled when the limit is saturated (instead of enqueued in “awaiting slot”).
- Consider whether a single global limit is necessary; per‑deployment limits or different global keys can reduce cross‑deployment contention.
Why starvation happens
- Slot acquisition is atomic and first‑come‑first‑served; there’s no round‑robin across deployments.
- Worker SQL ordering favors:
1) work queue priority,
2) earliest next_scheduled_start_time,
3) tie-break by insertion/ID timing.
- If A and B schedule at the same moment, tiny timing differences (or higher priority for A’s queue) can cause A to repeatedly win.
Docs for deeper reference
- Global concurrency limits: Global concurrency limits
- Work pools/queues and priority: Work pools
- Deployments (concurrency options): Deployments
- States (Late/Awaiting slot): States
- Settings reference (slot wait, lease durations): Settings reference
Re: “Cancel all” in the dashboard
Totally appreciate the offer. If you’ve found a case where the bulk cancel UI isn’t using the proper /set_state pathway (or not propagating to the server state machine), that’s worth fixing. If you can share a brief repro or a tiny patch, I can flag it to the team or open a PR/discussion to get it reviewed.