Repository navigation
fix(quota-share): release the winner's reserved in-flight slot (#11371) - #11408
diegosouzapw merged 2 commits into
Conversation
…souzapw#11371) selectQuotaShareTarget reserves an in-flight slot for its winner but no production caller ever invoked the returned release callback, so counters grew monotonically for the process lifetime: P2C degenerated into 'fewest lifetime dispatches' and the max_concurrent gate fail-opened past its cap permanently. Thread the idempotent release out of the ordering path: - applyStrategyOrdering now returns { orderedTargets, quotaShareRelease } (non-null only for the quota-share strategy) - resolveComboTargetPipeline releases it when a post-selection hard filter produces an earlyResponse and otherwise hands it to the host - handleComboChatInner invokes it in the dispatch finally and on the two early returns between resolution and the try block Regression tests pin the missing case from the issue: ordering through the real path leaves the counter back at zero, plus null-release for non-quota-share strategies.
abhisheksharma2411
left a comment
There was a problem hiding this comment.
The diagnosis in #11371 is exactly right and the fix shape is the correct one — threading the release out of applyStrategyOrdering rather than releasing inside it, making it idempotent, and firing it from finally plus both early exits. The interface doc explaining why dropping the callback degenerates P2C into "fewest lifetime dispatches" is the part that will stop this regressing again.
But there is an accidental deletion in this diff, and I think it is a real regression.
recordComboFailure is removed from the no-executable-targets path
if (orderedTargets.length === 0) {
// Surface a recovery hint + auto-clear the session pin after enough consecutive
// no-target failures (silent-stop fix). Threshold of 3 prevents a one-off account
// wipe from destroying the prompt-cache pin benefit on the next request.
- recordComboFailure(effectiveSessionId, combo.name);
+ // #11371: same early-exit release as the pinned-turn path above.
+ targetResolution.quotaShareRelease?.();The release call is correct and belongs there. It looks like it replaced the line instead of being added above it — the comment describing the auto-clear is still sitting on top of code that no longer does it.
Counting call sites in combo.ts:
base: 34 (import), 931, 1415, 2626, 2723, 2755 → 5 calls
#11408: 34 (import), 1419, 2630, 2727, 2759 → 4 calls
The four survivors are all on dispatch-failure paths. Nothing else records a failure for the "Combo has no executable targets" early return.
Why it matters: recordComboFailure is what increments the per-(session, combo) counter and clears the session's model pin once it reaches COMBO_FAILURE_THRESHOLD. With this call gone, a session that keeps hitting "no executable targets" never accumulates a streak, so the pin is never auto-cleared — which is the silent-stop the comment above it was added to fix. A user whose pinned target has gone away stays stuck, and the recovery hint in the 404 becomes the only way out.
And nothing catches it. I ran the combo unit suite on this branch: tests/unit/combo/*.test.ts → 162 tests, 162 pass, 0 fail, same as the base branch. There is a pin-recovery.test.ts, but it covers buildRecoveryHint and payload shape, not the recording call on this path. So the behaviour change is silent in CI as well as in the diff.
The fix is presumably just to keep both:
recordComboFailure(effectiveSessionId, combo.name);
// #11371: same early-exit release as the pinned-turn path above.
targetResolution.quotaShareRelease?.();If it was deliberate — say you judged that a no-target combo shouldn't count toward the pin-clear streak — then it's a separate behavioural change that deserves its own line in the description and its own test, because it is not implied by #11371.
On the rest
The finally placement looks right: quotaShareConcurrencyRelease?.() and the new quotaShareRelease?.() are different reservations (admission vs the P2C in-flight slot), and calling both is correct rather than redundant. Firing the release again from the early exits is safe because release latches on released, so the double-call from an early-exit path that later also unwinds through finally is a no-op — worth keeping that released flag prominent, since the whole design leans on it.
One question: selectQuotaShareTarget reserves the slot at selection time using the selection-time clock, but the request may sit in cooldown-retry for a while before dispatch. Is the lease long enough that a slow request's slot is never reaped as expired before its own release runs, or is the idempotent release the only thing keeping the count honest there? Not a defect I can point at — I ask because "reserve early, release in finally" is exactly where lease expiry and explicit release tend to disagree under load.
CI on this head currently shows Unit Tests fast-path (2/4) and (4/4) red plus No new ESLint warnings — I did not chase those, since the combo suite is green locally and they may be unrelated to this change.
CI triage (all four red checks investigated)
No code changes required for this PR; happy to follow up if maintainers see otherwise. |
|
Obrigado pela contribuição — o fix de vazamento do contador in-flight do quota-share (#11371) é real e valioso, mas encontramos um problema antes de mergear. No caminho Poderia ajustar para manter as duas chamadas ( |
|
Nice work — the reservation-leak mechanism matches the idempotent decrementInflight contract in quotaShareStrategy and the new ordering tests are exactly what #11371 asked for (45/45 green locally). One real regression to fix before merge: in handleComboChatInner's |
…he no-targets early exit The diegosouzapw#11371 slot-release replaced (instead of augmenting) the recordComboFailure call on the no-executable-targets path, freezing the pin auto-clear counter. Restore both effects side by side + regression guard in combo-routing-engine. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
6435f61
into
diegosouzapw:release/v3.8.51
Brings in base tip 6435f61 (diegosouzapw#11408 quota-share slot release). Reconcile the one CI-merge lint drift it introduced: diegosouzapw#11408 grew combo-routing-engine.test.ts (+40 lines, its own regression guard) with 3 new `any` casts + an unused `payload` binding, but did not bump the frozen no-explicit-any suppression (267). Dropped the unused payload assignment (kept the body-drain) and pruned the suppression to the real count (268). Also carries the two agent-card base-red test fixes surfaced by the fuller unit-shard run: - conductor-agent-card.test.ts: same stale request mock as agent-card-route (handler reads request.nextUrl); pass a makeRequest() stand-in. - pack-artifact-policy.test.ts: add bin/cli/utils/volatileEnvPath.mjs to the expected missing-required-paths list (matches the policy entry added earlier).
…souzapw#11371) (diegosouzapw#11408) Merged into release/v3.8.51. Batch review caught that the slot-release replaced the diegosouzapw#5923 recordComboFailure call on the no-executable-targets path (pin auto-clear would freeze); restored both effects side by side + regression guard in combo-routing-engine.test.ts (proven RED without the fix, GREEN after — 211/211 across the focused combo/quota/opencode battery, quota-share-strategy 31/31 on the final branch head). Thanks @oyi77!
Summary
Fixes #11371.
selectQuotaShareTargetreserves an in-flight slot for its winner and returns an idempotent release callback — but no production caller ever invoked it, so counters only grew: P2C degenerated from "least loaded" into "fewest lifetime dispatches" (overriding operator DRR weights) and the FASE 2.1max_concurrentgate fail-opened permanently past its cap.This threads the release out of the ordering path (shape 1 from the issue):
applyStrategyOrderingnow returns{ orderedTargets, quotaShareRelease }— non-null only when the quota-share strategy ranresolveComboTargetPipelinereleases it itself when a post-selection hard filter produces an earlyResponse, otherwise hands it to the hosthandleComboChatInnerinvokes it in the dispatchfinally(besidequotaShareConcurrencyRelease) and on the two early returns between resolution and the try blockThe callback is idempotent, so exactly-once is enforced at the source and defensive double-calls are harmless.
Tests
applyStrategyOrdering("quota-share", …)path reserves one slot and leaves the counter back at zero after release; double-release floors at 0; non-quota-share strategies return a null release (tests/unit/quota-share-strategy.test.ts)combo-apply-strategy-ordering-split.test.ts,prompt-cache-affinity.test.ts)Verification
Base:
release/v3.8.51(3192eb88d). No behavior change for non-quota-share strategies.