Relationships
#3294 WorkerGateway.dispatch leaks a capacity slot when recording the busy status fails
Opened by zack · 10/12/2026· Shipped 10/13/2026
Found by code reading while triaging #3293; not reproduced at runtime.
Summary
In src/serve/worker_gateway.ts (at dbd7ba9a), WorkerGateway.dispatch pushes the dispatch id into entry.activeDispatchIds and sets the status to busy (lines 466-467), then awaits the set_status transition (lines 468-478) and issues the dispatch credential (line 480). The try/finally that removes the id from activeDispatchIds only starts at line 485.
If the awaited set_status model method rejects (for example a datastore or lock error while writing the worker record), or issueForDispatch throws, dispatch rejects without ever entering the try block. The id stays in activeDispatchIds and the status stays busy for as long as the worker entry lives, so the worker permanently loses one slot (a capacity-1 worker becomes unschedulable) until it re-enrolls or serve restarts.
Expected
A dispatch that fails before it reaches the worker releases its slot, the same as one that fails afterwards.
Suggested direction
Move the push and the busy transition inside the try whose finally filters the id back out, or wrap lines 466-483 in their own cleanup. A unit test in src/serve/worker_gateway_test.ts can use a runModelMethod seam that rejects for set_status busy and assert that workers() reports no active dispatches afterwards.
Relation to #3293
Separate defect. #3293 is about the dispatcher double-counting a healthy running dispatch; this one is a slot that is never returned after a failed transition.
Shipped
Click a lifecycle step above to view its details.
Sign in to post a ripple.