Repository navigation
Conversation
Add BackoffConfig for tunable exponential backoff with jitter support in the Rust client SDK. Users can configure initial/max delays, multiplier, retry limits, and jitter factor via ClientConfig builder methods or per-connection overrides. - Replace hardcoded 1s/30s reconnect policy with configurable backoff - Add disable_reconnect() to prevent retries after connection drops - Add connect_with_backoff() for per-connection override - Enforce max_retries in the reconnect loop - Reset backoff state on successful reconnection - Add rand dependency (workspace) for jitter randomization - Add 17 tests (6 unit + 11 integration) covering progression, capping, retry limits, jitter bounds, normalization, and end-to-end reconnect behavior
| if attempt.did_open { | ||
| backoff.reset(); |
There was a problem hiding this comment.
🟠 Medium · `disable_reconnect` still reconnects after an established connection closes
When try_connect returns after a connection that reached Init, this branch resets the counter and returns to the outer loop, which immediately starts another connection attempt without consulting max_retries. Consequently max_retries = Some(0) only prevents retries after an opening failure; a successfully opened connection reconnects on every later disconnect, contrary to disable_reconnect's contract. Gate the transition back to try_connect with the retry budget, and add a test that opens successfully before the server closes the socket.
| let sleep_duration = if self.config.jitter_factor > 0.0 { | ||
| let jitter_offset = (rand::random::<f64>() * 2.0 - 1.0) * self.config.jitter_factor; | ||
| let factor = (1.0 + jitter_offset).max(0.0); | ||
| Duration::from_secs_f64((base.as_secs_f64() * factor).max(0.0)) |
There was a problem hiding this comment.
🟠 Medium · Jitter bypasses the configured maximum delay
Once the base reaches max_delay, positive jitter multiplies it by up to 1 + jitter_factor, so a 30-second maximum with standard jitter can sleep for almost 36 seconds, and a factor of 1.0 can nearly double it. This violates the documented ceiling and can also overflow Duration::from_secs_f64 for very large valid durations. Clamp the jittered result to config.max_delay before converting it back to Duration, and cover a step whose base is already at the cap.
| } | ||
| } | ||
|
|
||
| #[cfg(test)] |
There was a problem hiding this comment.
🔵 Low · Backoff tests are duplicated inside production source
The repository requires Rust tests under tests/, and this module duplicates the same progression, retry-limit, reset, jitter, clamping, and normalization coverage already added in tests/backoff.rs. Keeping both suites doubles maintenance while violating the crate's test-layout convention. Remove this inline module and keep the public-API coverage in tests/backoff.rs.
| if attempt.did_open { | ||
| backoff.reset(); | ||
|
|
||
| // After a successful connection that later closed, check | ||
| // if the retry budget allows another reconnect cycle. | ||
| // This is how disable_reconnect() (max_retries=0) stops | ||
| // reconnection after the initial connection drops. | ||
| if !backoff.can_retry() { | ||
| break 'keepalive; | ||
| } | ||
|
|
||
| break 'retry; |
There was a problem hiding this comment.
🟠 Medium · The first reconnect bypasses the configured delay and jitter
After an established connection closes, this jumps to the outer loop and calls try_connect() immediately; backoff.tick() only runs if that reconnect attempt then fails. As a result, initial_delay is not the documented delay before the first reconnect, and jitter cannot prevent a fleet of clients disconnected together from retrying simultaneously. For reconnect-enabled configurations, wait through the freshly reset backoff (while still selecting on disconnect/shutdown) before starting the next outer-loop attempt.
86b1371 to
b053c5e
Compare
…eepalive termination
b053c5e to
03476e0
Compare
Description
Replace the Rust client's hardcoded reconnect backoff policy with a configurable
BackoffConfig.Previously, the Rust client used a fixed exponential backoff of:
This made reconnect behavior difficult to tune for different applications and could cause multiple clients that fail at around the same time to retry on the same schedule.
This PR adds:
max_retriesjitter_factorClientConfigbuilders for configuring the default reconnect policydisable_reconnect()for disabling retries while still allowing the initial connection attemptconnect_with_backoff()for per-connection overridesExisting behavior is preserved by
BackoffConfig::default(), which uses the previous 1s / 30s / 2.0x policy with unlimited retries and no jitter.max_retriescounts retries after the initial connection attempt. For example,max_retries = Some(3)allows up to 4 total connection attempts.Related issue: None.
Type of change
How Has This Been Tested?
Ran the following locally:
cargo check -p rivetkit-client cargo fmt --all -- --check cargo clippy -p rivetkit-client --all-targets --all-features -- -D warnings cargo test -p rivetkit-clientTest coverage includes:
ActorHandleconfiguration propagationdisable_reconnect()performing the initial connection attempt exactly onceResults:
rivetkit-clienttests: all passingChecklist: