fix(storage): stop a stall verdict vouching for the connection it stalled on (DAB-1177) - #33
Open
matalina wants to merge 1 commit into
Open
fix(storage): stop a stall verdict vouching for the connection it stalled on (DAB-1177)#33matalina wants to merge 1 commit into
matalina wants to merge 1 commit into
Conversation
…lled on (DAB-1177) Review follow-up to #32, which merged before these landed. Neither bug is reachable at the shipped defaults — `transactionTimeout` is 0, so nothing arms the hard timer — but both bite the moment it is enabled. A guard's own timeout stamped `lastSettleAt`, so a stall counted as evidence the connection was alive for every sibling guard on it. The abort the timeout provokes made it worse: it arrives back as an ordinary `onabort` and stamped a second time. On a wedged connection with several operations in flight — the case this guard exists for — each timeout vouched for the rest and they kept deferring, up to `MAX_DEFER_WINDOWS` each. Bounded, so never a hang, but it delayed exactly the verdicts the guard is meant to deliver. Only genuine settles vouch now. The late-fire heuristic also compared against the window actually scheduled. After a defer that is only the remainder of a budget and can be a few milliseconds, so ordinary event-loop lag on a main thread busy with bulk IndexedDB work cleared it — misreading jitter as a suspended tab, spending the single re-arm, and stamping `lateFire` on an error measured wide awake, which corrupts the flag the duration stats are filtered by. Now measured against the full budget and required to clear an absolute floor as well. `budget` became write-only and is gone. Both fixes carry a regression test, each confirmed to fail without its fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #32, which merged before these two landed. Both were caught by @jacwright in review there, and both are real.
Neither is reachable at the shipped defaults —
transactionTimeoutis0, so nothing arms the hard timer. That is why they were correctly flagged non-blocking. They bite the moment it is enabled, which is DAB-1177 step 2.1. A stall verdict vouched for the connection it stalled on
The timeout path called
settleReject, which runs the wrappedsettleand stampslastSettleAt— so a guard firing counted as evidence the connection was alive, for every other guard on it.The abort made it worse than it first looks:
abortTargetprovokes anabortthat arrives back through the ordinaryonaborthandler and stamped a second time.On a wedged connection with several operations in flight — the exact case this guard exists for — each timeout vouched for the remaining ones and they kept deferring, up to
MAX_DEFER_WINDOWSapiece. Bounded, so never a hang, but it delayed precisely the verdicts the guard is meant to deliver.Now a
timedOutflag, set before the abort so neither the rejection nor the abort it provokes stamps. Only genuine settles vouch.2. Late-fire misread ordinary scheduling lag as a suspended tab
The heuristic compared elapsed time against the window actually scheduled. After a defer that window is only the remainder of a budget and can be a few milliseconds, so
budget * LATE_FIRE_FACTORwas trivially cleared by event-loop lag on a main thread busy with bulk IndexedDB work — misreading jitter as suspension, spending the single re-arm on a full extra window, and stampinglateFire: trueon an error measured wide awake. That last part is the damaging one:lateFireis the flag the duration stats get filtered by, so a wrong one quietly corrupts the measurement this whole feature exists to produce.Took both suggestions from the review rather than either: an absolute
LATE_FIRE_FLOOR_MS = 500and measuring against the fullhardBudget, factored into a sharedfiredLate().This also removed a latent flake in the existing suite —
reports a slow transactionused a 20ms budget and assertedlateFire === false, which 25ms of jitter would have flipped.budgetbecame write-only after the change and is gone.Testing
Both fixes carry a regression test. I verified they are not vacuous by reverting both fixes and re-running — exactly those two fail, and pass again once restored.