You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Under parallel-agent load on one root, configure (route bind) sits in the Mutating lane waiting for read_inflight/lsp_inflight to reach zero, and never gets in. Observed waits of 18s → 68s → 82s → 96s → 160s → 204s on a single root. Every phase that does real work reports 0ms:
No slow project walk, no gitignore scan, no DB open. All of it is admission wait.
Because the daemon's route-bind relay times out well before that, new binds fail while already-bound routes keep working — so it presents as "aft is broken for new sessions" while existing sessions are fine.
Two distinct mechanisms, not one
The blocker census distinguishes them, and the split matters because a single fix only addresses one:
16x waiting_on_readers <- reader churn: many short reads, none individually old
3x waiting_on_readers_stuck(...) <- one long-lived reader, named
(a) Reader-churn starvation — 16 occurrences, the 68–96s band. Admission is reader-preferring with no fairness (executor/mod.rs:2078,2090):
A new PureRead is admitted whenever the cap allows. Nothing consults the Mutating queue, so a continuous stream of short reads keeps read_inflight above zero indefinitely and the queued writer is lapped forever. Self-reinforcing: more parallel sessions → denser reads → longer starvation.
A write barrier already exists but is scoped one class too narrowly (executor/mod.rs:~1200):
A queued interactive writer blocks maintenance from starting, but not interactive reads. The concept is in the codebase; it just doesn't cover the case that starves.
(b) A single long reader on the LSP lane — 3 occurrences, and they are the WORST waits (160s, 204s). All three name the same lane:
has_epoch_reader includes lsp_inflight, so one 60s+ LSP-status job gates the Mutating lane on its own. Write-preferring admission does not fix this — with a single reader holding for 66 seconds, the writer waits 66 seconds no matter how it is prioritised.
Why the split is the point
The obvious fix (make admission write-preferring or fair) resolves (a) and leaves (b) — which produced the two longest stalls observed. Worth treating as two items:
Fair/write-preferring admission: extend the existing barrier so a queued interactive Mutating job blocks new PureRead admissions. Needs a bound so a permanent write barrier can't stall reads indefinitely, but the mechanism is already there for maintenance.
Sibling, not duplicate. #242 is a running writer (blocking-fresh inspect) holding the lane while unrelated mutations queue. This is a queued writer that can't get the lane because readers never yield. Same admission design, opposite ends; different fixes.
The two also compound: the 60s+ SerialLspStatus reader here and the 63s inspect in #242 are on the same root, in the same window.
Environment and attribution
v0.51.2 (aa027c6f), Linux, subc daemon transport, ~9 concurrent sessions across ~6 roots, several parallel implementer agents on /…/openai-auth invalidating files every 1-2s. Not resource pressure: 38G RAM available, load 3.04, daemon at 2.6G.
Starvation mechanism (a) was diagnosed independently by a peer operator working the daemon side, from the ack_ready/blockers timings; I verified it against the admission code and added the (b) split from the stuck-reader census.
Under parallel-agent load on one root,
configure(route bind) sits in the Mutating lane waiting forread_inflight/lsp_inflightto reach zero, and never gets in. Observed waits of 18s → 68s → 82s → 96s → 160s → 204s on a single root. Every phase that does real work reports 0ms:No slow project walk, no gitignore scan, no DB open. All of it is admission wait.
Because the daemon's route-bind relay times out well before that, new binds fail while already-bound routes keep working — so it presents as "aft is broken for new sessions" while existing sessions are fine.
Two distinct mechanisms, not one
The blocker census distinguishes them, and the split matters because a single fix only addresses one:
(a) Reader-churn starvation — 16 occurrences, the 68–96s band. Admission is reader-preferring with no fairness (
executor/mod.rs:2078,2090):A new
PureReadis admitted whenever the cap allows. Nothing consults the Mutating queue, so a continuous stream of short reads keepsread_inflightabove zero indefinitely and the queued writer is lapped forever. Self-reinforcing: more parallel sessions → denser reads → longer starvation.A write barrier already exists but is scoped one class too narrowly (
executor/mod.rs:~1200):A queued interactive writer blocks maintenance from starting, but not interactive reads. The concept is in the codebase; it just doesn't cover the case that starves.
(b) A single long reader on the LSP lane — 3 occurrences, and they are the WORST waits (160s, 204s). All three name the same lane:
has_epoch_readerincludeslsp_inflight, so one 60s+ LSP-status job gates the Mutating lane on its own. Write-preferring admission does not fix this — with a single reader holding for 66 seconds, the writer waits 66 seconds no matter how it is prioritised.Why the split is the point
The obvious fix (make admission write-preferring or fair) resolves (a) and leaves (b) — which produced the two longest stalls observed. Worth treating as two items:
PureReadadmissions. Needs a bound so a permanent write barrier can't stall reads indefinitely, but the mechanism is already there for maintenance.SerialLspStatusjob holdinglsp_inflightblocks every route bind on the root. Either bound it, or let it wait without holding the epoch-reader gate — the same shape as LspManager mutex is held across the post-edit diagnostics wait (up to 10s), serializing every other LSP operation #216's parked-waiter fix.Relationship to #242
Sibling, not duplicate. #242 is a running writer (blocking-fresh inspect) holding the lane while unrelated mutations queue. This is a queued writer that can't get the lane because readers never yield. Same admission design, opposite ends; different fixes.
The two also compound: the 60s+
SerialLspStatusreader here and the 63s inspect in #242 are on the same root, in the same window.Environment and attribution
v0.51.2 (
aa027c6f), Linux, subc daemon transport, ~9 concurrent sessions across ~6 roots, several parallel implementer agents on/…/openai-authinvalidating files every 1-2s. Not resource pressure: 38G RAM available, load 3.04, daemon at 2.6G.Starvation mechanism (a) was diagnosed independently by a peer operator working the daemon side, from the
ack_ready/blockers timings; I verified it against the admission code and added the (b) split from the stuck-reader census.