Skip to content

Fix corrupted or incomplete files after interrupted download - #359

Merged
iLLiCiTiT merged 3 commits into
bugfix/transfer-max-retriesfrom
bugfix/download-resume-integrity
Sep 14, 2026
Merged

iLLiCiTiT merged 3 commits into
bugfix/transfer-max-retriesfrom
bugfix/download-resume-integrity

Conversation

@BigRoy

@BigRoy BigRoy commented Sep 12, 2026

Copy link
Copy Markdown
Member

Stacked on #358 (same download/upload loop). Review only the last commit; the base changes to develop once that PR is merged.

Bug

When a download is interrupted and retried, the resulting file can be corrupted or incomplete without any error:

  1. Duplicated content — the retry sends Range: bytes=<offset>-. If the server (or a proxy) ignores Range and answers 200 with the whole file, the whole file is appended after the already written part.
  2. Incomplete file accepted — if the connection is closed before all content arrives without raising (or raises ChunkedEncodingError, which wasn't caught), the download is reported as finished.
  3. Content-Length was stored in progress as a string, and a response without it (chunked transfer) raised KeyError.

Cause

  • Response status was never checked for 206 Partial Content before appending.
  • Received bytes were never compared with the expected size.
  • response.headers["Content-length"] was passed directly to set_content_size.

Fix

  • If continuing (offset > 0) and the response is not 206, truncate the output stream and reset progress, then write the full content.
  • After the response is consumed, compare transferred bytes with the content size. A mismatch raises ConnectionError, which is retried with Range like other connection errors. ChunkedEncodingError is retried as well.
  • Content-Length is converted to int (for 206 the already downloaded offset is added) and a missing header is allowed.

Reproduce

Hard to trigger on demand against a real server, tests/test_download_resume.py simulates:

  • a response closed after 4 of 10 bytes, continued with a 206,
  • a server ignoring Range (develop result: b"01230123456789"),
  • a response without Content-Length (develop: KeyError).

Testing notes

  • Normal downloads: con.download_file(...) of a thumbnail/file gives identical bytes and progress.content_size is now an int.
  • Manual interruption: download a large file through a proxy that drops the connection (e.g. toxiproxy limit_data toxic), compare the checksum with the source file. Test once with a proxy that strips Range to cover the 200 case.

🤖 Generated with Claude Code

- Download continuation with 'Range' header did not check the response
  is partial content (206). A server ignoring 'Range' sends the whole
  file, which was appended to already downloaded content.
- Downloaded size was never compared to 'Content-Length', so a download
  closed before all content was received was accepted as complete. It
  is now continued as another attempt, also on ChunkedEncodingError.
- 'Content-Length' was stored as string and missing header raised
  KeyError.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@BigRoy
BigRoy added this pull request to stack #362 September 12, 2026 22:18
@BigRoy
BigRoy removed this pull request from stack #362 September 14, 2026 14:41
@BigRoy
BigRoy requested review from iLLiCiTiT and a lite review from Copilot September 14, 2026 14:42
@BigRoy BigRoy self-assigned this Sep 14, 2026
@BigRoy BigRoy added the type: bug Something isn't working label Sep 14, 2026
@BigRoy
BigRoy marked this pull request as ready for review September 14, 2026 14:43

Copilot AI 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.

🟡 Changes recommended

Clear the stale Range header after resetting progress and add the requested chunked-error regression test.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Fixes resumable downloads by detecting incomplete responses, handling ignored ranges, and supporting missing Content-Length headers.

Changes:

  • Resets partial output when resumed responses are not 206.
  • Validates transfer sizes and retries incomplete downloads.
  • Adds regression tests for interrupted and range-ignored downloads.
File summaries
File Summary
tests/test_download_resume.py Adds interrupted-download regression coverage.
ayon_api/server_api.py Implements resume integrity checks and retry handling; a critical stale Range header issue remains, and ChunkedEncodingError coverage is missing.
Review details

Suppressed comments (1)

ayon_api/server_api.py:1723

  • The new ChunkedEncodingError branch is not exercised by these regression tests: all FakeResponse iterators complete normally, so a future removal or misordering of this exception handler could go unnoticed. Add a response/iterator that raises ChunkedEncodingError after yielding a prefix and assert the retry uses the Range offset and produces the complete content.
                requests.exceptions.ChunkedEncodingError,
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread ayon_api/server_api.py
Comment thread ayon_api/server_api.py Outdated

Copilot AI 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.

🟡 Changes recommended

Correct Content-Length parsing and missing-header handling before approval.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread ayon_api/server_api.py
@iLLiCiTiT
iLLiCiTiT merged commit 0d325fc into bugfix/transfer-max-retries Sep 14, 2026
@iLLiCiTiT
iLLiCiTiT deleted the bugfix/download-resume-integrity branch September 14, 2026 15:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants