Skip to content

HTTP Client TCP Keep Alives & Explicit Hostname Type#345

Open
malcolmgreaves wants to merge 5 commits intomainfrom
mg/http_client_rework_v2
Open

HTTP Client TCP Keep Alives & Explicit Hostname Type#345
malcolmgreaves wants to merge 5 commits intomainfrom
mg/http_client_rework_v2

Conversation

@malcolmgreaves
Copy link
Copy Markdown
Collaborator

Second attempt at PR #325

Fixes critical bug in #325 -- the root cause of that was improperly setting the auth token to the
right hostname. When using a remote repository to create the HTTP client, #325 was incorrectly
getting the full URL and using that. Instead, it should have been getting only the hostname.

This PR removes the confusion that led to the bug. The hostname is no longer typed as a String.
Instead, it is represented by a new struct Hostname, which has explicit host, port, and
scheme fields as well as a .hostname() method that gets the correct host and, if non-none,
appends the port to it.

The problem was that we were incorrectly using the full repository URL instead of supplying just the host.

This caused the auth to fail because it was trying to provide an auth token for the repository url instead of the host (i.e. hub.oxen.ai/api/ox/repo instead of hub.oxen.ai).

To prevent confusion, `String` is no longer used to indicate the host.
Instead, the dedicated `Host` enum from `url::Url` is used.

All call-sites have bene updated to properly parse the URL and extract the host.

If there's no host in the URL, then the new `OxenError::NoHost` variant is returned.

A better design would be to keep `Url`'s present instead of `String`.
The best design would be a new wrapper for `Url` that would always have the `scheme` and `host` present, since we never want to use a repository URL that doesn't have a host (and a URL can be valid w/o having a host).
@malcolmgreaves malcolmgreaves changed the title Mg/http client rework v2 HTTP Client TCP Keep Alives & Explicit Hostname Type Mar 17, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 17, 2026

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added retry mechanism with exponential backoff for network operations to improve reliability.
    • Introduced improved URL parsing with explicit port support.
    • Added configurable connection timeout and keep-alive settings via environment variables.
  • Bug Fixes

    • Enhanced error handling for invalid or missing host information in URLs.
    • Improved HTTP client configuration for better transfer stability.
  • Refactor

    • Restructured HTTP client creation and initialization patterns.
    • Updated default HTTP timeout from 600 to 120 seconds for faster failure detection.

Walkthrough

This pull request refactors Oxen's HTTP client infrastructure by introducing a Hostname abstraction for URL parsing, a centralized retry module with exponential backoff, and new URL-based client constructors (new_for_url_transfer, new_for_url, etc.) to replace builder patterns. Public API signatures for RemoteRepository methods (host(), port(), scheme()) are changed to return Result types, and multipart upload flows are updated to use streaming with retry integration.

Changes

Cohort / File(s) Summary
Client Infrastructure Refactoring
oxen-rust/crates/lib/src/api/client.rs, oxen-rust/crates/lib/src/api/client/retry.rs
Introduces Hostname struct for port-aware URL parsing, new RetryConfig and with_retry async function with exponential backoff, and new public client constructors (new_for_url, new_for_url_transfer, new_for_host_transfer) replacing builder patterns. Removes get_scheme_and_host_from_url and reworks authentication handling with token injection and result-based user-agent building.
Multipart Upload Refactoring
oxen-rust/crates/lib/src/api/client/file.rs, oxen-rust/crates/lib/src/api/client/import.rs
Changes multipart upload signatures to accept references; replaces file-path-based Part creation with streaming stream_with_length for retry-safe uploads. Integrates centralized retry mechanism and introduces make_multipart_form helper for import workflow. Updates put_multipart_file, make_file_part, and apply_commit_body signatures.
Retry Integration Across Operations
oxen-rust/crates/lib/src/api/client/commits.rs, oxen-rust/crates/lib/src/api/client/versions.rs
Replaces manual exponential backoff loops with centralized retry::with_retry mechanism. Updates upload_single_tarball_to_server_with_retry, upload_data_chunk_to_server_with_retry, download_data_from_version_paths, and batch upload functions to use new retry infrastructure. Switches to Hostname-based client creation.
Client Creation Consolidation
oxen-rust/crates/lib/src/api/client/tree.rs, oxen-rust/crates/lib/src/api/client/commits.rs, oxen-rust/crates/lib/src/api/client/workspaces/files.rs, oxen-rust/crates/lib/src/core/v_latest/push.rs
Replaces builder_for_* patterns and timeout configurations with new new_for_url_transfer and new_for_host_transfer constructors, streamlining client instantiation across multiple modules.
URL/Host Parsing Updates
oxen-rust/crates/cli/src/cmd/clone.rs, oxen-rust/crates/cli/src/helpers.rs, oxen-rust/crates/lib/src/api/client/repositories.rs, oxen-rust/crates/lib/src/model/repository/remote_repository.rs, oxen-rust/crates/lib/src/model/repository/repo_new.rs
Replaces get_scheme_and_host_from_url with Hostname-based parsing. Updates RemoteRepository.host(), .port(), .scheme() to return Result types. Adjusts callers in clone, helpers, and repository modules to use new Hostname abstraction.
Configuration and Error Handling
oxen-rust/crates/lib/src/constants.rs, oxen-rust/crates/lib/src/error.rs, oxen-rust/crates/lib/src/config/auth_config.rs, oxen-rust/crates/lib/src/util.rs, oxen-rust/crates/lib/src/util/internal_types.rs
Reduces default HTTP timeout from 600s to 120s; introduces DEFAULT_CONNECT_TIMEOUT_SECS and DEFAULT_TCP_KEEPALIVE_SECS with environment variable readers. Adds NoHost error variant. Changes auth_token_for_host signature from generic to concrete &str. Introduces HasLen trait with macro implementations for streaming support.
Test Updates and Utilities
oxen-rust/crates/lib/src/api/client/compare.rs, oxen-rust/crates/lib/src/repositories/workspaces/upload.rs, oxen-rust/crates/lib/src/test.rs
Strengthens test assertions with debug messages, adjusts test setup to propagate Result types from host()? and scheme()?, and refactors async test result handling for better error reporting.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • jcelliott
  • rpschoenburg

🐰 A hostname to parse with care,
Retries spring up everywhere,
Streaming uploads with grace so fine,
URLs dancing in a single line,
Error handling now complete and rare! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: introducing TCP keep-alives for the HTTP client and adding an explicit Hostname type to fix auth-related bugs.
Description check ✅ Passed The description directly relates to the changeset by explaining the bug fix (improper auth token assignment) and the solution (Hostname struct with explicit host/port/scheme fields).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch mg/http_client_rework_v2
📝 Coding Plan
  • Generate coding plan for human review comments

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can customize the high-level summary generated by CodeRabbit.

Configure the reviews.high_level_summary_instructions setting to provide custom instructions for generating the high-level summary.

@malcolmgreaves
Copy link
Copy Markdown
Collaborator Author

This works with auth:

$ oxen-dev push
Warning: 🐂 Oxen remote version mismatch.

CLI Version: 0.46.3
Server Version: 0.46.2

Please visit https://docs.oxen.ai/getting-started/install for installation instructions.

🐂 oxen push origin main -> adb01f32ce28f4dec76125baf26818a8
🐂 push complete 🎉 took 324ms

This is for a remote repository in the hub:

$ cat .oxen/config.toml
remote_name = "origin"
min_version = "0.36.0"

[[remotes]]
name = "origin"
url = "https://hub.oxen.ai/malcolm-oxen/deleteme"

[storage]
type = "local"

[storage.settings]
path = ".oxen/versions/files"

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (6)
oxen-rust/crates/lib/src/api/client/compare.rs (1)

399-399: Prefer a named constant for the test loop bound.

Using a small local constant (e.g., TOTAL_DIRS) would make this less magic-number-y and keep comment/logic aligned if the count changes later.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@oxen-rust/crates/lib/src/api/client/compare.rs` at line 399, Replace the
magic loop bound "5" with a named constant to clarify intent: add a const (e.g.,
const TOTAL_DIRS: usize = 5;) near the containing test or module scope and
change the loop from for i in 0..5 to for i in 0..TOTAL_DIRS; use the symbol
TOTAL_DIRS in any related comments or assertions so the test count stays
consistent if it changes later (refer to the loop in compare.rs where the for i
in 0..5 occurs).
oxen-rust/crates/lib/src/util/internal_types.rs (1)

4-6: Consider adding is_empty() method to avoid Clippy warnings.

Clippy's len_without_is_empty lint typically warns when a len() method is provided without a corresponding is_empty() method. While this trait is pub(crate), adding is_empty() would make it more complete and avoid potential lint warnings if callers need to check emptiness.

♻️ Optional: Add is_empty method
 /// Indicates that the type has a length.
 pub(crate) trait HasLen {
     fn len(&self) -> usize;
+    fn is_empty(&self) -> bool {
+        self.len() == 0
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@oxen-rust/crates/lib/src/util/internal_types.rs` around lines 4 - 6, The
HasLen trait exposes fn len(&self) -> usize but is missing is_empty(), which
triggers clippy's len_without_is_empty; add fn is_empty(&self) -> bool to the
trait with a default implementation that returns self.len() == 0, and update any
existing impls only if they override is_empty; ensure the trait signature
(HasLen) and the len method remain unchanged while providing the default
is_empty implementation so callers get the convenience and clippy warning is
avoided.
oxen-rust/crates/lib/src/model/repository/remote_repository.rs (1)

72-72: Consider caching the parsed Hostname to avoid repeated parsing.

Each of host(), port(), and scheme() independently parses self.remote.url twice (once to url::Url, then via Hostname::from_url). If these methods are called together or frequently, this creates redundant parsing overhead.

♻️ Potential optimization: Add a helper method
+    fn hostname(&self) -> Result<Hostname, OxenError> {
+        Hostname::from_url(&self.remote.url.parse()?)
+    }
+
     pub fn host(&self) -> Result<String, OxenError> {
-        let hn = Hostname::from_url(&self.remote.url.parse()?)?;
-        Ok(hn.hostname())
+        Ok(self.hostname()?.hostname())
     }
 
     pub fn port(&self) -> Result<Option<u16>, OxenError> {
-        let hn = Hostname::from_url(&self.remote.url.parse()?)?;
-        Ok(hn.port)
+        Ok(self.hostname()?.port)
     }
 
     pub fn scheme(&self) -> Result<String, OxenError> {
-        let hn = Hostname::from_url(&self.remote.url.parse()?)?;
-        Ok(hn.scheme)
+        Ok(self.hostname()?.scheme)
     }

This doesn't reduce parsing when methods are called individually, but improves readability. For true caching, consider storing Hostname as a lazily-computed field.

Also applies to: 78-78, 84-84

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@oxen-rust/crates/lib/src/model/repository/remote_repository.rs` at line 72,
The Hostname parsing is repeated in host(), port(), and scheme(): avoid
redundant url parsing by introducing a single helper on RemoteRepository (e.g.,
a private method like parsed_hostname(&self) -> Result<Hostname, Error>) that
parses self.remote.url once using self.remote.url.parse()? and
Hostname::from_url, and have host(), port(), and scheme() call that helper; for
stronger optimization consider making Hostname a lazily-computed field on the
struct (e.g., Option<Hostname> with initialization on first access) so
subsequent calls reuse the parsed Hostname instead of reparsing.
oxen-rust/crates/lib/src/api/client/versions.rs (1)

199-208: Redundant authentication error handling in retry closure.

The with_retry function already handles OxenError::Authentication specially (returning immediately without retry). The explicit match arms on lines 203-204 are redundant since the error will be returned as-is by with_retry.

♻️ Simplified retry closure
     let config = retry::RetryConfig::default();
     retry::with_retry(&config, |_attempt| async {
-        match try_download_data_from_version_paths(remote_repo, hashes, local_repo).await {
-            Ok(val) => Ok(val),
-            Err(OxenError::Authentication(val)) => Err(OxenError::Authentication(val)),
-            Err(err) => Err(err),
-        }
+        try_download_data_from_version_paths(remote_repo, hashes, local_repo).await
     })
     .await
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@oxen-rust/crates/lib/src/api/client/versions.rs` around lines 199 - 208, The
retry closure redundantly matches OxenError::Authentication even though
retry::with_retry already treats that variant specially; simplify the closure
inside retry::with_retry(&config, |_attempt| async { ... }) by directly
returning the Result from try_download_data_from_version_paths(remote_repo,
hashes, local_repo).await (i.e., propagate Ok or Err as-is) instead of matching
and re-wrapping OxenError::Authentication; this removes the unnecessary match
while keeping the call sites try_download_data_from_version_paths and error
propagation unchanged.
oxen-rust/crates/lib/src/api/client/file.rs (1)

96-108: Unnecessary async on synchronous function.

The make_file_part function is marked async but contains no await points. This adds unnecessary overhead from the async state machine.

♻️ Remove unnecessary async
 /// Create a Part in a multipart Form from the specified data.
 /// Intended to be used for uploading a single file.
-async fn make_file_part<T: Into<reqwest::Body> + HasLen>(
+fn make_file_part<T: Into<reqwest::Body> + HasLen>(
     file_data: T,
     file_name: Option<&str>,
 ) -> Result<Part, OxenError> {

And update the call site on line 84:

-            let file_part = make_file_part(file_data, file_name).await?;
+            let file_part = make_file_part(file_data, file_name)?;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@oxen-rust/crates/lib/src/api/client/file.rs` around lines 96 - 108, The
function make_file_part is declared async but has no await points; remove the
async keyword and make it a regular synchronous function returning Result<Part,
OxenError>, and update any call sites that currently use .await when invoking
make_file_part to call it synchronously (remove the .await). Locate the function
by its name make_file_part and update callers that reference it (remove awaiting
and adjust error handling if needed).
oxen-rust/crates/lib/src/api/client.rs (1)

184-197: build_user_agent returns Result but never errors.

The function signature returns Result<String, OxenError> but the implementation only ever returns Ok(...). This could be simplified to return String directly, though it may be intentionally prepared for future fallibility.

♻️ Optional: Simplify return type if no errors expected
-fn build_user_agent(config: &RuntimeConfig) -> Result<String, OxenError> {
+fn build_user_agent(config: &RuntimeConfig) -> String {
     let host_platform = config.host_platform.display_name();
     let runtime_name = match config.runtime_name {
         Runtime::CLI => config.runtime_name.display_name().to_string(),
         _ => format!(
             "{} {}",
             config.runtime_name.display_name(),
             config.runtime_version
         ),
     };
-    Ok(format!(
-        "{USER_AGENT}/{VERSION} ({host_platform}; {runtime_name})"
-    ))
+    format!("{USER_AGENT}/{VERSION} ({host_platform}; {runtime_name})")
 }

And update call site on line 141:

-        let user_agent = build_user_agent(&config)?;
+        let user_agent = build_user_agent(&config);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@oxen-rust/crates/lib/src/api/client.rs` around lines 184 - 197, The
build_user_agent function currently returns Result<String, OxenError> but never
errors; change its signature to fn build_user_agent(config: &RuntimeConfig) ->
String and return the formatted string directly (remove Ok(...)). Then update
all callers that expect a Result (e.g., the caller that previously used
build_user_agent(...)?) to accept a String instead (remove any unwrapping or ?
handling) so call sites compile with the new return type.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@oxen-rust/crates/lib/src/api/client/versions.rs`:
- Around line 479-493: The loop in versions.rs currently iterates with
0..max_retries, producing only max_retries attempts which is inconsistent with
the centralized with_retry (which uses 0..=config.max_retries to produce
max_retries+1 attempts); update the retry loop that calls multipart_batch_upload
(and uses variables max_retries, retry::exponential_backoff, BASE_WAIT_TIME,
MAX_WAIT_TIME) to use 0..=max_retries so it performs the same total number of
attempts as with_retry, keeping the existing “Don't sleep after the last
attempt” check intact.

---

Nitpick comments:
In `@oxen-rust/crates/lib/src/api/client.rs`:
- Around line 184-197: The build_user_agent function currently returns
Result<String, OxenError> but never errors; change its signature to fn
build_user_agent(config: &RuntimeConfig) -> String and return the formatted
string directly (remove Ok(...)). Then update all callers that expect a Result
(e.g., the caller that previously used build_user_agent(...)?) to accept a
String instead (remove any unwrapping or ? handling) so call sites compile with
the new return type.

In `@oxen-rust/crates/lib/src/api/client/compare.rs`:
- Line 399: Replace the magic loop bound "5" with a named constant to clarify
intent: add a const (e.g., const TOTAL_DIRS: usize = 5;) near the containing
test or module scope and change the loop from for i in 0..5 to for i in
0..TOTAL_DIRS; use the symbol TOTAL_DIRS in any related comments or assertions
so the test count stays consistent if it changes later (refer to the loop in
compare.rs where the for i in 0..5 occurs).

In `@oxen-rust/crates/lib/src/api/client/file.rs`:
- Around line 96-108: The function make_file_part is declared async but has no
await points; remove the async keyword and make it a regular synchronous
function returning Result<Part, OxenError>, and update any call sites that
currently use .await when invoking make_file_part to call it synchronously
(remove the .await). Locate the function by its name make_file_part and update
callers that reference it (remove awaiting and adjust error handling if needed).

In `@oxen-rust/crates/lib/src/api/client/versions.rs`:
- Around line 199-208: The retry closure redundantly matches
OxenError::Authentication even though retry::with_retry already treats that
variant specially; simplify the closure inside retry::with_retry(&config,
|_attempt| async { ... }) by directly returning the Result from
try_download_data_from_version_paths(remote_repo, hashes, local_repo).await
(i.e., propagate Ok or Err as-is) instead of matching and re-wrapping
OxenError::Authentication; this removes the unnecessary match while keeping the
call sites try_download_data_from_version_paths and error propagation unchanged.

In `@oxen-rust/crates/lib/src/model/repository/remote_repository.rs`:
- Line 72: The Hostname parsing is repeated in host(), port(), and scheme():
avoid redundant url parsing by introducing a single helper on RemoteRepository
(e.g., a private method like parsed_hostname(&self) -> Result<Hostname, Error>)
that parses self.remote.url once using self.remote.url.parse()? and
Hostname::from_url, and have host(), port(), and scheme() call that helper; for
stronger optimization consider making Hostname a lazily-computed field on the
struct (e.g., Option<Hostname> with initialization on first access) so
subsequent calls reuse the parsed Hostname instead of reparsing.

In `@oxen-rust/crates/lib/src/util/internal_types.rs`:
- Around line 4-6: The HasLen trait exposes fn len(&self) -> usize but is
missing is_empty(), which triggers clippy's len_without_is_empty; add fn
is_empty(&self) -> bool to the trait with a default implementation that returns
self.len() == 0, and update any existing impls only if they override is_empty;
ensure the trait signature (HasLen) and the len method remain unchanged while
providing the default is_empty implementation so callers get the convenience and
clippy warning is avoided.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ea8fe52c-e5ef-4512-b42b-28870dd7557a

📥 Commits

Reviewing files that changed from the base of the PR and between 50d3858 and 408be7a.

📒 Files selected for processing (22)
  • oxen-rust/crates/cli/src/cmd/clone.rs
  • oxen-rust/crates/cli/src/helpers.rs
  • oxen-rust/crates/lib/src/api/client.rs
  • oxen-rust/crates/lib/src/api/client/commits.rs
  • oxen-rust/crates/lib/src/api/client/compare.rs
  • oxen-rust/crates/lib/src/api/client/file.rs
  • oxen-rust/crates/lib/src/api/client/import.rs
  • oxen-rust/crates/lib/src/api/client/repositories.rs
  • oxen-rust/crates/lib/src/api/client/retry.rs
  • oxen-rust/crates/lib/src/api/client/tree.rs
  • oxen-rust/crates/lib/src/api/client/versions.rs
  • oxen-rust/crates/lib/src/api/client/workspaces/files.rs
  • oxen-rust/crates/lib/src/config/auth_config.rs
  • oxen-rust/crates/lib/src/constants.rs
  • oxen-rust/crates/lib/src/core/v_latest/push.rs
  • oxen-rust/crates/lib/src/error.rs
  • oxen-rust/crates/lib/src/model/repository/remote_repository.rs
  • oxen-rust/crates/lib/src/model/repository/repo_new.rs
  • oxen-rust/crates/lib/src/repositories/workspaces/upload.rs
  • oxen-rust/crates/lib/src/test.rs
  • oxen-rust/crates/lib/src/util.rs
  • oxen-rust/crates/lib/src/util/internal_types.rs

Comment on lines +479 to 493
for attempt in 0..max_retries {
files_to_retry =
multipart_batch_upload(local_repo, remote_repo, chunk, client, files_to_retry).await?;

if !files_to_retry.is_empty() {
let wait_time = exponential_backoff(BASE_WAIT_TIME, retry_count, MAX_WAIT_TIME);
sleep(Duration::from_millis(wait_time as u64)).await;
if files_to_retry.is_empty() {
return Ok(());
}

// Don't sleep after the last attempt
if attempt + 1 < max_retries {
let wait_time =
retry::exponential_backoff(BASE_WAIT_TIME as u64, attempt, MAX_WAIT_TIME as u64);
sleep(Duration::from_millis(wait_time)).await;
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Off-by-one inconsistency with centralized retry module.

The loop uses 0..max_retries which gives exactly max_retries attempts. However, the centralized with_retry function uses 0..=config.max_retries (line 37 of retry.rs), giving max_retries + 1 total attempts (1 initial + max_retries retries).

This inconsistency could lead to this function attempting one fewer retry than expected compared to other retry-enabled operations.

🔧 Proposed fix for consistency
-    for attempt in 0..max_retries {
+    for attempt in 0..=max_retries {
         files_to_retry =
             multipart_batch_upload(local_repo, remote_repo, chunk, client, files_to_retry).await?;
 
         if files_to_retry.is_empty() {
             return Ok(());
         }
 
         // Don't sleep after the last attempt
-        if attempt + 1 < max_retries {
+        if attempt < max_retries {
             let wait_time =
                 retry::exponential_backoff(BASE_WAIT_TIME as u64, attempt, MAX_WAIT_TIME as u64);
             sleep(Duration::from_millis(wait_time)).await;
         }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for attempt in 0..max_retries {
files_to_retry =
multipart_batch_upload(local_repo, remote_repo, chunk, client, files_to_retry).await?;
if !files_to_retry.is_empty() {
let wait_time = exponential_backoff(BASE_WAIT_TIME, retry_count, MAX_WAIT_TIME);
sleep(Duration::from_millis(wait_time as u64)).await;
if files_to_retry.is_empty() {
return Ok(());
}
// Don't sleep after the last attempt
if attempt + 1 < max_retries {
let wait_time =
retry::exponential_backoff(BASE_WAIT_TIME as u64, attempt, MAX_WAIT_TIME as u64);
sleep(Duration::from_millis(wait_time)).await;
}
}
for attempt in 0..=max_retries {
files_to_retry =
multipart_batch_upload(local_repo, remote_repo, chunk, client, files_to_retry).await?;
if files_to_retry.is_empty() {
return Ok(());
}
// Don't sleep after the last attempt
if attempt < max_retries {
let wait_time =
retry::exponential_backoff(BASE_WAIT_TIME as u64, attempt, MAX_WAIT_TIME as u64);
sleep(Duration::from_millis(wait_time)).await;
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@oxen-rust/crates/lib/src/api/client/versions.rs` around lines 479 - 493, The
loop in versions.rs currently iterates with 0..max_retries, producing only
max_retries attempts which is inconsistent with the centralized with_retry
(which uses 0..=config.max_retries to produce max_retries+1 attempts); update
the retry loop that calls multipart_batch_upload (and uses variables
max_retries, retry::exponential_backoff, BASE_WAIT_TIME, MAX_WAIT_TIME) to use
0..=max_retries so it performs the same total number of attempts as with_retry,
keeping the existing “Don't sleep after the last attempt” check intact.

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