Skip to content

feat(rivetkit-client): configurable reconnect backoff - #5852

Open
Blazearth wants to merge 2 commits into
rivet-dev:mainfrom
Blazearth:feat/client-reconnect-backoff
Open

Blazearth wants to merge 2 commits into
rivet-dev:mainfrom
Blazearth:feat/client-reconnect-backoff

Conversation

@Blazearth

Copy link
Copy Markdown

Description

Replace the Rust client's hardcoded reconnect backoff policy with a configurable BackoffConfig.

Previously, the Rust client used a fixed exponential backoff of:

  • Initial delay: 1s
  • Maximum delay: 30s
  • Multiplier: 2.0x
  • Unlimited retries
  • No jitter

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:

  • Configurable initial and maximum retry delays
  • Configurable exponential multiplier
  • Optional retry limits via max_retries
  • Optional symmetric jitter via jitter_factor
  • ClientConfig builders for configuring the default reconnect policy
  • disable_reconnect() for disabling retries while still allowing the initial connection attempt
  • connect_with_backoff() for per-connection overrides
  • Backoff state reset after a successful connection
  • Configuration normalization/clamping for direct struct construction and builder usage

Existing behavior is preserved by BackoffConfig::default(), which uses the previous 1s / 30s / 2.0x policy with unlimited retries and no jitter.

max_retries counts 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

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

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-client

Test coverage includes:

  • Default backoff behavior matching the previous hardcoded policy
  • Exponential backoff progression and maximum-delay capping
  • Retry-limit enforcement
  • Backoff state reset
  • Jitter bounds and variation
  • Builder clamping and configuration normalization
  • Client → ActorHandle configuration propagation
  • Per-connection backoff overrides
  • End-to-end reconnect behavior using a local test server
  • disable_reconnect() performing the initial connection attempt exactly once
  • Retry limits correctly stopping the reconnect loop

Results:

  • 6 dedicated unit tests: all passing
  • 11 dedicated integration tests: all passing
  • 41 total rivetkit-client tests: all passing
  • No new compiler or Clippy warnings

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

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

@the-company-company the-company-company Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 2 medium · 🔵 1 low

Reviewed commit 4e9e978.

Comment on lines 723 to +724
if attempt.did_open {
backoff.reset();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 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.

Comment on lines +190 to +193
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))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 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)]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 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.

@the-company-company the-company-company Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 1 medium-severity finding

Reviewed commit 86b1371.

Comment on lines 723 to 734
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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 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.

@Blazearth
Blazearth force-pushed the feat/client-reconnect-backoff branch from 86b1371 to b053c5e Compare October 8, 2026 15:36

@the-company-company the-company-company Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 1 medium-severity finding

Reviewed commit b053c5e.

Comment thread rivetkit-rust/packages/client/src/connection.rs
@Blazearth
Blazearth force-pushed the feat/client-reconnect-backoff branch from b053c5e to 03476e0 Compare October 8, 2026 15:38

@the-company-company the-company-company Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No issues found

Reviewed commit 03476e0.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant