Skip to content

Conversation

@virajbhartiya
Copy link

@virajbhartiya virajbhartiya commented Oct 23, 2025

Implements a shared reqwest::Client instance to centralize HTTP client usage across the desktop app.

Changes:

  • Add shared_client.rs module with global client and host-specific retryable clients
  • Update web_api.rs, upload.rs, and captions.rs to use shared client
  • Configure exponential backoff retry policy (5 max retries per host)
  • Enable global request tracking for DOS protection

Benefits:

  • Improved retry policies with exponential backoff
  • Enhanced DOS protection through global request tracking
  • Better performance by reusing client instances
  • Centralized HTTP client configuration

Closes #1135

Summary by CodeRabbit

  • Chores
    • Centralized HTTP client with shared retry logic to improve network resilience and reduce resource usage across the desktop app.
    • Uploads and API requests now use the shared client with enhanced automatic recovery for transient network failures and rate limiting (up to 5 retries); no user-facing behavior changes.
  • Style
    • Minor formatting tweaks to UI type declarations (no behavior change).

@virajbhartiya
Copy link
Author

@oscartbeaumont can you please review this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 23, 2025

Walkthrough

Adds a lazily-initialized global reqwest::Client with retry policies, replaces local client construction across captions/upload/web_api to use the shared client (adjusting web_api builder signatures to accept &reqwest::Client), and normalizes quote style in four TypeScript type import declarations.

Changes

Cohort / File(s) Change Summary
Shared HTTP client
apps/desktop/src-tauri/src/shared_client.rs, apps/desktop/src-tauri/src/lib.rs
New module registered in lib.rs; implements a global SHARED_CLIENT (OnceLock) and exposes get_shared_client() and get_retryable_client() returning a shared reqwest::Client (30s timeout, cross-host retry policy).
Replace per-scope client usage
apps/desktop/src-tauri/src/captions.rs, apps/desktop/src-tauri/src/upload.rs, apps/desktop/src-tauri/src/web_api.rs
Replaced local reqwest::Client creation with calls to get_shared_client() / get_retryable_client(...). web_api.rs builder closures now accept &reqwest::Client. Uploads extract host and obtain retryable client via get_retryable_client.
Type declaration formatting
packages/ui-solid/src/auto-imports.d.ts
Converted double quotes to single quotes in four typeof import(...)['default'] type import declarations (stylistic only).

Sequence Diagram(s)

sequenceDiagram
    participant Caller as Caller (captions / upload / web_api)
    participant Shared as shared_client
    participant Lock as OnceLock
    participant HTTP as reqwest::Client

    Caller->>Shared: get_shared_client() / get_retryable_client(host)
    alt first initialization
        Shared->>Lock: get_or_init()
        Lock->>HTTP: build Client (timeout=30s, retry policy)
        HTTP-->>Lock: Client instance
        Lock-->>Shared: &'static Client
    else cached
        Lock-->>Shared: &'static Client
    end
    Shared-->>Caller: &'static Client
    Caller->>HTTP: perform request using shared client
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Extra attention: shared_client.rs retry policy details, changes to web_api.rs builder signatures, and upload presigned URL host extraction/error mapping.

Possibly related PRs

Suggested labels

codex

Suggested reviewers

  • Brendonovich

Poem

🐰 I found a client, warm and shared,
OnceLock cradled, carefully prepared.
Retries counted, hops kept light,
Uploads, captions — all take flight.
A little rabbit cheers tonight.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning The pull request contains out-of-scope changes in packages/ui-solid/src/auto-imports.d.ts, which modifies quote styles in TypeScript type import declarations (double quotes to single quotes for four icon imports). These changes are purely formatting/style adjustments unrelated to the shared HTTP client implementation and have no connection to issue #1135's objectives about reqwest::Client management, retry policies, or DOS protection. This file modification appears to be an incidental commit that should be excluded from the PR scope.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "feat: implement shared reqwest::Client for improved retry policies and DOS protection" clearly and accurately summarizes the main change: introducing a shared HTTP client with centralized configuration. The title is specific and concise, avoiding vague terms, and directly reflects the primary objective of consolidating client creation and improving retry behavior as described in the PR objectives. The core changes across multiple files (captions.rs, upload.rs, web_api.rs, and the new shared_client.rs module) all align with this stated purpose.
Linked Issues Check ✅ Passed The pull request successfully addresses all coding-related requirements from issue #1135. The implementation introduces a globally shared reqwest::Client via OnceLock (stopping local client instantiation across captions.rs, upload.rs, and web_api.rs), centralizes HTTP configuration in the new shared_client.rs module with consistent exponential backoff retry policies (max 5 retries), and enables global request tracking through the for_all_hosts retry classification for DOS protection. The refactored code paths replace all local ClientBuilder constructions with calls to get_shared_client() or get_retryable_client(host), directly fulfilling the stated objectives of centralized configuration, client reuse, and improved retry/DOS behavior.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link
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: 5

🧹 Nitpick comments (1)
apps/desktop/src-tauri/src/shared_client.rs (1)

11-18: Consider adding connection pool configuration.

The shared client uses default connection pooling settings. For a desktop application making concurrent uploads/downloads, you might want to tune these parameters.

 pub fn get_shared_client() -> &'static Client {
     SHARED_CLIENT.get_or_init(|| {
         Client::builder()
+            .pool_max_idle_per_host(10)
+            .pool_idle_timeout(Some(std::time::Duration::from_secs(90)))
             .timeout(std::time::Duration::from_secs(30))
             .build()
             .expect("Failed to create shared HTTP client")
     })
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d81b2ae and 108ba60.

📒 Files selected for processing (6)
  • apps/desktop/src-tauri/src/captions.rs (2 hunks)
  • apps/desktop/src-tauri/src/lib.rs (1 hunks)
  • apps/desktop/src-tauri/src/shared_client.rs (1 hunks)
  • apps/desktop/src-tauri/src/upload.rs (3 hunks)
  • apps/desktop/src-tauri/src/web_api.rs (5 hunks)
  • packages/ui-solid/src/auto-imports.d.ts (4 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Format Rust code using rustfmt and ensure all Rust code passes workspace-level clippy lints.
Rust modules should be named with snake_case, and crate directories should be in kebab-case.

Files:

  • apps/desktop/src-tauri/src/lib.rs
  • apps/desktop/src-tauri/src/shared_client.rs
  • apps/desktop/src-tauri/src/web_api.rs
  • apps/desktop/src-tauri/src/upload.rs
  • apps/desktop/src-tauri/src/captions.rs
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use a 2-space indent for TypeScript code.
Use Biome for formatting and linting TypeScript/JavaScript files by running pnpm format.

Use strict TypeScript and avoid any; leverage shared types

Files:

  • packages/ui-solid/src/auto-imports.d.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Use kebab-case for filenames for TypeScript/JavaScript modules (e.g., user-menu.tsx).
Use PascalCase for React/Solid components.

Files:

  • packages/ui-solid/src/auto-imports.d.ts
🧬 Code graph analysis (3)
apps/desktop/src-tauri/src/web_api.rs (1)
apps/desktop/src-tauri/src/shared_client.rs (1)
  • get_shared_client (11-18)
apps/desktop/src-tauri/src/upload.rs (1)
apps/desktop/src-tauri/src/shared_client.rs (2)
  • get_retryable_client (50-52)
  • get_retryable_client_builder (25-44)
apps/desktop/src-tauri/src/captions.rs (1)
apps/desktop/src-tauri/src/shared_client.rs (1)
  • get_shared_client (11-18)
🔇 Additional comments (4)
apps/desktop/src-tauri/src/lib.rs (1)

24-24: LGTM: Module declaration is correct.

The shared_client module declaration is properly positioned and follows Rust naming conventions.

apps/desktop/src-tauri/src/web_api.rs (1)

61-80: LGTM: Proper client centralization.

The changes correctly centralize HTTP client usage and improve the API by making the client dependency explicit through the function signature. This also improves testability.

apps/desktop/src-tauri/src/captions.rs (1)

1072-1077: Review concern about 30-second timeout is valid but requires design decision.

The shared client is configured with a 30-second timeout, and model file sizes range from 75 MB (tiny) to 3.1 GB (large-v3). For a 466 MB file (small model) at typical home broadband speeds (10 Mbps), the download alone would require ~373 seconds, far exceeding the 30-second timeout. At 100 Mbps (fiber), even then most models exceed the timeout window.

The download function currently has no retry mechanism or streaming with resume capability. Any timeout will immediately fail with an error message rather than retry or resume.

This is a legitimate concern IF your target users download medium-to-large models. However, this may be intentional by design (restricting to tiny/base models, or requiring users with fast connections). You should verify:

  1. What model size(s) do you expect users to download?
  2. What is your minimum target internet speed?
  3. Should larger models use a longer timeout, resumable downloads, or background service?
apps/desktop/src-tauri/src/shared_client.rs (1)

25-44: The review comment is incorrect.

The reqwest::retry module with for_host() function is a valid API in reqwest 0.12, where for_host() returns a retry Builder that can be configured and attached to a ClientBuilder. The code uses the correct pattern and will compile successfully. No code changes are required, and no additional dependencies need to be added to Cargo.toml.

The codebase already has the correct dependency (reqwest = { version = "0.12.7", ... }), and the implementation in shared_client.rs properly uses the retry builder API with appropriate configuration methods like .classify_fn(), .max_retries_per_request(), and .max_extra_load().

Likely an incorrect or invalid review comment.

Copy link
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 (1)
apps/desktop/src-tauri/src/shared_client.rs (1)

36-42: Consider removing the unused _host parameter.

The function signature suggests host-specific behavior, but the _host parameter is ignored and the same shared client is returned for all hosts. This creates a misleading API that may confuse callers.

Consider one of these options:

  1. Preferred: Remove the parameter entirely if host-specific clients aren't needed:
-pub fn get_retryable_client(_host: String) -> Result<&'static Client, reqwest::Error> {
+pub fn get_retryable_client() -> &'static Client {
-    Ok(get_shared_client())
+    get_shared_client()
 }
  1. Alternative: If keeping for API compatibility, add a comment explaining why the parameter exists but is unused:
+/// Note: The `host` parameter is currently unused but retained for API compatibility.
+/// All requests share the same client instance for consistent DOS protection.
 pub fn get_retryable_client(_host: String) -> Result<&'static Client, reqwest::Error> {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 108ba60 and d2b4c4a.

📒 Files selected for processing (2)
  • apps/desktop/src-tauri/src/shared_client.rs (1 hunks)
  • apps/desktop/src-tauri/src/upload.rs (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Format Rust code using rustfmt and ensure all Rust code passes workspace-level clippy lints.
Rust modules should be named with snake_case, and crate directories should be in kebab-case.

Files:

  • apps/desktop/src-tauri/src/upload.rs
  • apps/desktop/src-tauri/src/shared_client.rs
🧬 Code graph analysis (1)
apps/desktop/src-tauri/src/upload.rs (1)
apps/desktop/src-tauri/src/shared_client.rs (1)
  • get_retryable_client (40-42)
🔇 Additional comments (3)
apps/desktop/src-tauri/src/shared_client.rs (1)

11-34: LGTM! Shared client addresses previous architectural concern.

The single shared client with for_all_hosts() retry policy now provides the global request tracking mentioned in the PR objectives. This design properly addresses the previous review concern about per-host clients breaking global tracking.

apps/desktop/src-tauri/src/upload.rs (2)

715-722: Previous concern addressed: proper host validation.

The code now correctly returns an error when the host is missing instead of using a "<unknown>" fallback. This ensures per-host retry limiting works as intended.


801-806: Previous concern addressed: proper host validation (singlepart).

Similar to the multipart uploader, this code now correctly returns an error when the host is missing, ensuring consistent behavior across both upload paths.

Copy link
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: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a1e3dea and d3ea053.

📒 Files selected for processing (2)
  • apps/desktop/src-tauri/src/shared_client.rs (1 hunks)
  • apps/desktop/src-tauri/src/upload.rs (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/desktop/src-tauri/src/upload.rs
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Format Rust code using rustfmt and ensure all Rust code passes workspace-level clippy lints.
Rust modules should be named with snake_case, and crate directories should be in kebab-case.

Files:

  • apps/desktop/src-tauri/src/shared_client.rs
🔇 Additional comments (1)
apps/desktop/src-tauri/src/shared_client.rs (1)

1-5: LGTM! Idiomatic use of OnceLock for thread-safe lazy initialization.

The use of OnceLock for a globally shared client is the modern, idiomatic approach in Rust.

Comment on lines +11 to +37
pub fn get_shared_client() -> &'static Client {
SHARED_CLIENT.get_or_init(|| {
Client::builder()
.retry(
reqwest::retry::for_all()
.classify_fn(|req_rep| {
match req_rep.status() {
// Server errors and rate limiting
Some(s)
if s.is_server_error()
|| s == reqwest::StatusCode::TOO_MANY_REQUESTS =>
{
req_rep.retryable()
}
// Network errors
None => req_rep.retryable(),
_ => req_rep.success(),
}
})
.max_retries_per_request(5)
.max_extra_load(5.0),
)
.timeout(std::time::Duration::from_secs(30))
.build()
.expect("Failed to create shared HTTP client")
})
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify the reqwest::retry::for_all() API and consider error handling.

The retry configuration looks reasonable, but I'd like to verify the API usage:

  1. The reqwest::retry::for_all() call on line 15 should be confirmed to exist in the version of reqwest being used. Standard reqwest doesn't include a built-in retry module by default—this typically requires the reqwest-retry crate or similar middleware.

  2. The .expect() on line 35 will panic if client creation fails during initialization. While this may be acceptable for a critical shared resource, consider whether a more graceful startup failure would be appropriate.

Run the following script to verify the reqwest retry API:

Also, please verify the retry API documentation:


🏁 Script executed:

#!/bin/bash
# Check Cargo.toml for reqwest and retry-related dependencies
rg -n "reqwest" apps/desktop/src-tauri/Cargo.toml -A 3

Length of output: 361


🌐 Web query:

reqwest retry for_all method documentation

💡 Result:

Do you mean the built-in reqwest retry API (reqwest::retry) or the reqwest-retry middleware (crate reqwest-retry)? Which crate and version are you using?

For reference:

Reply with which one (and the version) and I’ll find the exact for_all documentation or show how to use it.


Excellent! I found critical information. The retry module was added to reqwest via PR #2763, which includes a new ClientBuilder::retry() method and a reqwest::retry module to build a retry policy, with a for_host() function and classify_fn() method. However, I need to verify when this was released and whether it's in reqwest 0.12.7.Perfect! I found it. As of version 0.12.23, reqwest includes automatic retry functionality - which means reqwest 0.12.7 does NOT have the built-in retry module. The PR #2763 added it, but it was only released in 0.12.23 or later.

Let me verify the exact API by checking if for_all() is the correct method name:

Looking at the search results, I found a critical issue. The code uses reqwest::retry::for_all(), but the built-in retry functionality was only added to reqwest starting in version 0.12.23, and the version specified in Cargo.toml is 0.12.7. Additionally, the API examples show for_host(), not for_all(). Let me check the actual imports in the source file to be certain:Based on my research, I now have sufficient evidence to write the rewritten review comment. The critical issue is that reqwest's automatic retry functionality was only added in version 0.12.23, but the codebase uses reqwest 0.12.7. The code calls reqwest::retry::for_all(), which does not exist in that version and would not compile.

Code will not compile with reqwest 0.12.7—retry module is unavailable.

The code uses reqwest::retry::for_all() on line 15, but automatic retry functionality was added to reqwest only in version 0.12.23. The Cargo.toml specifies reqwest 0.12.7, which does not include the reqwest::retry module.

Upgrade reqwest to at least 0.12.23 to enable the built-in retry API. Additionally, verify that the for_all() method is the correct API (the PR documentation references for_host() for host-specific retry policies).

🤖 Prompt for AI Agents
In apps/desktop/src-tauri/src/shared_client.rs around lines 11 to 37, the code
calls reqwest::retry::for_all() but the project’s Cargo.toml pins reqwest to
0.12.7 which does not include the retry module (added in >=0.12.23) and may use
a different API (examples show for_host()). Fix by updating Cargo.toml to
reqwest >=0.12.23 (or latest 0.12.x that includes retry), run cargo update, and
then verify/adjust the retry call to the correct API (e.g., use for_host() or
the actual factory function provided by that reqwest version) so the code
compiles.

Comment on lines +39 to +45
/// Get a retryable client for specific hosts
///
/// This function returns the shared client which has global retry tracking.
/// All requests use the same client instance for consistent DOS protection.
pub fn get_retryable_client(_host: String) -> Result<&'static Client, reqwest::Error> {
Ok(get_shared_client())
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Misleading API: unused parameter and infallible Result type.

The function signature has two issues that make the API confusing:

  1. The _host parameter is completely unused (indicated by the underscore prefix). If the host information isn't needed, this parameter should be removed.

  2. The function returns Result<&'static Client, reqwest::Error> but can never return an Err variant—it always returns Ok(get_shared_client()). This is misleading to callers who might write error-handling code that will never execute.

Consider one of these approaches:

Option 1 (Recommended): Simplify the API

If the host parameter truly isn't needed, remove it and change the return type:

-pub fn get_retryable_client(_host: String) -> Result<&'static Client, reqwest::Error> {
-    Ok(get_shared_client())
+pub fn get_retryable_client() -> &'static Client {
+    get_shared_client()
 }

Then update callers in upload.rs to not pass the host parameter.

Option 2: Remove the wrapper entirely

If get_retryable_client is just an alias for get_shared_client, consider removing it and having callers use get_shared_client() directly.

Option 3: Document the intent

If the signature is intentionally kept for future extensibility or API stability, add a comment explaining why the parameter is unused and why a Result is returned.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/desktop/src-tauri/src/shared_client.rs around lines 39–45, the function
get_retryable_client currently accepts an unused _host: String and returns a
Result that is never Err; remove the unused parameter and simplify the signature
to return &'static Client (no Result), i.e. pub fn get_retryable_client() ->
&'static Client { get_shared_client() }, then update all callers (e.g., in
upload.rs) to stop passing a host and to handle the direct client return;
alternatively if you prefer removing the wrapper, replace calls to
get_retryable_client() with get_shared_client() instead.

@oscartbeaumont
Copy link
Member

I'm closing this as:

  • It doesn't compile due to relying on functionality that doesn't exist in reqwest
  • It uses a global static and we would prefer it in Tauri state

Thanks though!

@virajbhartiya virajbhartiya deleted the refactor/scope branch October 28, 2025 09:34
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.

Shared reqwest::Client

2 participants