Skip to content

fix(ex_app): retry model downloads when the host throttles - #460

Merged
oleksandr-nc merged 3 commits into
mainfrom
fix/model-download-retry
Aug 11, 2026
Merged

fix(ex_app): retry model downloads when the host throttles#460
oleksandr-nc merged 3 commits into
mainfrom
fix/model-download-retry

Conversation

@oleksandr-nc

@oleksandr-nc oleksandr-nc commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

huggingface.co answers 429 to unauthenticated downloads under load, and a single such answer aborted fetch_models_task, failing the whole ExApp init with ModelFetchError: ... returned (429) <!DOCTYPE html>. It is also the most frequent reason our own CI goes red: 4 of the 5 recent Generate coverage report (2) failures I sampled were exactly this.

  • retry the statuses that mean "try again" (408, 425, 429, 500, 502, 503, 504)
  • follow Retry-After when the server sends a usable one, exponential backoff otherwise, both capped at 60s so a hoster asking for an hour cannot hang an init
  • 5 extra attempts by default, per-model override with the new max_retries download option, 0 restores the old behaviour
  • statuses that will not change (404) still fail on the first answer

Verified against a local HTTP server that throttles like the real one: before, 1 request then ModelFetchError; after, the download succeeds on the 3rd request, and a permanently throttling host still fails, after 6 requests. Plus 7 unit tests covering attempt counts, the Retry-After forms and the backoff.

Summary by CodeRabbit

  • New Features

    • Direct model downloads now automatically retry temporary failures and rate-limit responses.
    • Retry delays honor valid Retry-After values and use capped exponential backoff.
    • Added a per-model option to configure the maximum number of retries.
  • Bug Fixes

    • Improved resilience when downloading models from hosts experiencing throttling or temporary outages.
    • Existing download validation remains supported, including file size, ETag, and checksum checks.
  • Documentation

    • Added release notes and guidance for configuring download retries.

huggingface.co answers 429 to unauthenticated downloads under load, and a
single such answer aborted fetch_models_task, so the whole ExApp init failed.
It is also the most frequent reason our own CI goes red.

Retry the statuses that mean "try again" (408, 425, 429 and 5xx), following
Retry-After when the server sends a usable one and backing off exponentially
otherwise. Waits are capped at a minute so a hoster asking for an hour cannot
hang an init, the number of extra attempts defaults to 5 and can be set per
model with the new max_retries option. Statuses that will not change, 404 for
example, still fail on the first answer.

Extract the "file already on disk" check into a helper to keep the download
function within the local-variable budget.

Signed-off-by: Oleksandr Piskun <oleksandr2088@icloud.com>
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@oleksandr-nc, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 4 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 29134488-6a51-4e1b-ad54-02f80554308d

📥 Commits

Reviewing files that changed from the base of the PR and between 8921470 and ce15278.

📒 Files selected for processing (1)
  • tests_unit/test_fetch_model_file.py
📝 Walkthrough

Walkthrough

Direct model downloads now retry selected transient HTTP responses. The flow supports bounded Retry-After delays, exponential backoff, configurable per-model retry counts, and existing-file validation.

Changes

Direct model download retries

Layer / File(s) Summary
Retry configuration and documentation
nc_py_api/ex_app/integration_fastapi.py, CHANGELOG.md
Defines retry statuses, default retry counts, and maximum delays. Documents the max_retries option and retry behavior.
Retrying direct downloads
nc_py_api/ex_app/integration_fastapi.py
Calculates delays from numeric or HTTP-date Retry-After values. Retries transient responses, closes failed responses, logs retries, and preserves ETag, size, and SHA-256 validation.
Retry behavior tests
tests_unit/test_fetch_model_file.py
Tests response closure, retry limits, zero retries, non-retryable statuses, Retry-After formats, delay capping, invalid headers, and exponential backoff.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant fetch_models_task
  participant niquests.get
  participant ModelHost
  fetch_models_task->>niquests.get: Request direct model URL
  niquests.get->>ModelHost: Fetch model
  ModelHost-->>niquests.get: Return transient or successful response
  niquests.get-->>fetch_models_task: Return response
  fetch_models_task->>fetch_models_task: Apply bounded delay
  fetch_models_task->>fetch_models_task: Validate existing file
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: retrying model downloads when hosts throttle requests.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/model-download-retry

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.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests_unit/test_fetch_model_file.py`:
- Around line 222-251: Update FakeResponse and TestFetchModelRetries to track
response closure by adding a close() method that records invocation, then retain
references to the throttled responses and assert each is closed before the
subsequent request. Ensure the test exposes missing close() calls rather than
allowing contextlib.suppress to hide them.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 030031db-6c4b-4961-b9e0-001737ea6b12

📥 Commits

Reviewing files that changed from the base of the PR and between 2fc9b3f and f321e53.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • nc_py_api/ex_app/integration_fastapi.py
  • tests_unit/test_fetch_model_file.py

Comment thread tests_unit/test_fetch_model_file.py
@codecov

codecov Bot commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.89%. Comparing base (786d102) to head (ce15278).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #460      +/-   ##
==========================================
+ Coverage   94.79%   94.89%   +0.09%     
==========================================
  Files          50       50              
  Lines        5767     5875     +108     
==========================================
+ Hits         5467     5575     +108     
  Misses        300      300              
Files with missing lines Coverage Δ
nc_py_api/ex_app/integration_fastapi.py 60.35% <100.00%> (+8.48%) ⬆️
tests_unit/test_fetch_model_file.py 98.03% <100.00%> (+0.98%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

The retry loop closes a throttled answer before asking again, because its body
is never read and the connection would otherwise stay checked out. Nothing
verified that: the fake response had no close(), and suppressing every
exception around the call meant even a misspelled method name kept the tests
green while leaking a connection per retry in production.

Track close() on the fake and assert it, and narrow the guard to OSError so
transport errors are still ignored while a wrong call surfaces.

Signed-off-by: Oleksandr Piskun <oleksandr2088@icloud.com>

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
nc_py_api/ex_app/integration_fastapi.py (2)

287-291: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Redact the model URL before logging.

If model_path is a signed URL, Lines [289]-[290] log its query credentials on every retry. This exposes credentials even when the download later succeeds. Log only a redacted host and path. Apply the same redaction to any final failure message that contains the direct URL.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nc_py_api/ex_app/integration_fastapi.py` around lines 287 - 291, Update the
retry warning in the download flow around model_path to log only its redacted
host and path, never query credentials from a signed URL. Apply the same
redaction to the final failure message that reports the direct URL, reusing the
existing URL-redaction utility if available.

315-322: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Avoid mutating models_to_fetch options.

set_handlers reuses the same mapping for each /init request. __fetch_model_as_file removes save_path and max_retries, then fetch_models_task adds path. A second request loses the configured save path and retry count. Copy the options before modifying them. Add a repeated-init test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nc_py_api/ex_app/integration_fastapi.py` around lines 315 - 322, In
__fetch_model_as_file, copy the model options mapping before popping save_path
and max_retries so the shared models_to_fetch configuration remains unchanged
across /init requests. Preserve fetch_models_task’s path handling, and add a
test that performs repeated initialization with the same options and verifies
the configured save path and retry count are retained.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests_unit/test_fetch_model_file.py`:
- Around line 242-256: The test_retries_until_the_download_succeeds test does
not enforce the documented five-retry default. Extend throttled to five
responses before FakeResponse, then assert mocked.call_count is 6 and
sleep.call_count is 5 while preserving the existing connection-closure
assertions.

---

Outside diff comments:
In `@nc_py_api/ex_app/integration_fastapi.py`:
- Around line 287-291: Update the retry warning in the download flow around
model_path to log only its redacted host and path, never query credentials from
a signed URL. Apply the same redaction to the final failure message that reports
the direct URL, reusing the existing URL-redaction utility if available.
- Around line 315-322: In __fetch_model_as_file, copy the model options mapping
before popping save_path and max_retries so the shared models_to_fetch
configuration remains unchanged across /init requests. Preserve
fetch_models_task’s path handling, and add a test that performs repeated
initialization with the same options and verifies the configured save path and
retry count are retained.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 06d1dfa7-35f1-4cb8-bdd0-9948b2710770

📥 Commits

Reviewing files that changed from the base of the PR and between f321e53 and 8921470.

📒 Files selected for processing (2)
  • nc_py_api/ex_app/integration_fastapi.py
  • tests_unit/test_fetch_model_file.py

Comment thread tests_unit/test_fetch_model_file.py
Nothing enforced the default that the docstring and the changelog promise:
lowering it to three or four kept every test green. Exhaust it instead of
satisfying it, since serving a success after five throttled answers also passes
with a larger default, the loop returning as soon as it gets one.

Signed-off-by: Oleksandr Piskun <oleksandr2088@icloud.com>
@oleksandr-nc
oleksandr-nc merged commit 37f8cf7 into main Aug 11, 2026
21 checks passed
@oleksandr-nc
oleksandr-nc deleted the fix/model-download-retry branch August 11, 2026 11:25
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