Repository navigation
feat(net)!: resolve the reload backoff the way moq-tokio does - #6756
Conversation
Quest-Claim: fa818bb7390d6d62
The reload loop counted a session as healthy once it outlived `delay.initial`, so the first retry delay also decided which sessions reset the backoff. Add `delay.healthy` (default 1000ms, must be positive), matching moq-tokio's `connect.backoff.healthy`. Also drop `extensions` from `ReloadProps`: the loop never forwarded it to connect(), so it was accepted and ignored. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 29 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: 34e52d6
No actionable bugs found. Separating the healthy-session threshold from the initial retry delay is the right direction: both ordinary disconnects and immediate GOAWAY damping use the new threshold, while redirect-loop detection remains independent. The mocked-time cases distinguish escalating short-lived sessions from healthy resets. Validating the threshold at construction and removing the ignored extensions option keep the configuration clearer.
Verification: static review of the patch, surrounding reconnect/redirect logic, clock validation and public Connection option forwarding. I did not run tests, builds or network scenarios.
A zero `delay.max` capped every retry after the first at zero, so the loop redialed as fast as it could. moq-tokio treats a zero max as uncapped and caps the first delay too when max is below initial; do the same, and fall back to the healthy bar for redirect-loop runs when uncapped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: f1502af
No new actionable bugs found since the previous review (34e52d6). Treating max: 0 as uncapped and applying a positive cap to the first retry removes the documented configuration inconsistencies. The shared cap helper keeps retry growth and redirect settling coherent, and the expanded mocked-time cases cover both changes. The clock backend already splits oversized host timers, so uncapped growth does not immediately wrap into rapid retries.
Verification: static review of the incremental changes, surrounding retry/redirect logic and clock timer backend. I did not run tests or network scenarios.
Resolve ReloadDelay through one pacing() mirroring Rust's Pacing: - a multiplier below 1 keeps the delay constant, instead of collapsing it to zero or shrinking it; - an uncapped delay saturates instead of overflowing and stalling the loop; - a negative, NaN, or infinite field (or a non-finite multiplier) is refused when the loop is built, instead of silently meaning "unlimited" or failing later inside the effect. Match the retry window too: it counts from the first dial of a run, a wait that reaches the deadline gives up there instead of dialing, and a peer restarting through immediate empty GOAWAYs escalates the backoff but restarts the window rather than spending it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: 58b5379
No new actionable bugs found since reviewed f1502af. Centralizing pacing validation and growth is clearer than maintaining separate interpretations across retry and migration paths. The update adds multiplier clamping and overflow-safe growth, starts the retry budget with the first dial, and distinguishes empty-URI restarts from failed redirects while retaining escalating backoff. The new tests cover deadline boundaries, invalid configuration and repeated restarts.
Verification: static review of the incremental changes, surrounding retry/migration logic and corresponding Rust pacing/restart behavior. I did not run tests or exercise browser/network timing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Merged
(Written by Claude Opus 5.5) |
Problem
@moq/net's reload backoff (ReloadDelay, exposed asConnection.Backoff) disagreed with moq-tokio'sconnect.backofffield by field:backoff.initialintobackoff.healthy(moq-dev/moq#6529), but JS counted any session that outliveddelay.initialas healthy.maxcapped every retry after the first at zero (Rust: uncapped), and amaxbelowinitialdid not cap the first delay.multiplierbelow 1 collapsed or shrank the delay (Rust: treated as 1); an uncapped delay could overflow and stall the loop with an error inside the effect (Rust saturates).timeoutsilently meant "unlimited", and other invalid fields failed only later, inside the effect.ReloadPropsacceptedextensions, which the loop never forwarded toconnect().Approach
pacing()resolvesReloadDelayagainst its defaults with Rust'sPacingmeaning (initialcapped bymax, zeromaxuncapped, multiplier at least 1, saturating growth,settledfalling back tohealthywhen uncapped). It validates when the loop is built and is re-resolved per use, so an edit todelaystill applies to the next retry.delay.healthy(default 1000ms) gates the backoff/window reset and immediate-GOAWAY damping. Zero is refused, as in Rust.restartingpath does.extensionsfromReloadProps. The moq-net exports quest makesConnectProps.extensions@internal, so it is not forwarded.healthy: 50bars,max: 0,maxbelowinitial, multiplier 0 and 0.5), saturation, the retry window table (finishes an attempt under way, gives up at the deadline), restart GOAWAYs, and a refusal test for every field's invalid values. Each one excepthealthy: 50fails onmain. Migrate tests that relied oninitialdoubling as the bar now sethealthy.quest/m1/js-reload-config.md.Kept different from Rust on purpose:
timeoutdefaults to 0 (unlimited) rather than 10s, since a page usually has nothing that would redial a given-up loop (documented onDEFAULT_DELAY).Impact
@moq/net(breaking):Connection.Backoffgainshealthy. A session now counts as healthy afterhealthy(default 1000ms) rather than afterinitial. To keep the old behavior, setdelay.healthyto yourdelay.initial.@moq/net(breaking):delay.max: 0now means no cap instead of a zero delay; pass a small positivemaxfor near-immediate retries. Amaxbelowinitialnow caps the first delay as well.@moq/net(breaking): amultiplierbelow 1 now keeps the delay constant; useinitial: 0for immediate retries.@moq/net(breaking): a negative, NaN, or infiniteinitial,max,healthy, ortimeout, a zerohealthy, or a non-finitemultiplierthrowsRangeErrorfromnew Connection/new Reload. Use0for an uncappedmaxor unlimitedtimeout.@moq/net(behavior):delay.timeoutcounts from the first dial, so a hanging first dial spends it; a run gives up at the deadline instead of making one last attempt there; a peer draining sessions with an empty GOAWAY no longer spends it. Lengthentimeoutby one connect attempt to keep the old reach.@moq/net(internal):ReloadPropsno longer acceptsextensions, which was ignored. Drop the option.Alternatives
extensionsinstead of omitting it: rejected while the API audit makes it@internal.Follow-ups
None.
Checks:
just check(js check, js tests, markdown, quest) passed;bun test src/connection/214 pass.(Written by Claude Opus 5.5)
🤖 Generated with Claude Code