Skip to content

[utils] Download to a temporary file, so an interrupted download leaves no truncated file - #2204

Merged
felixdittrich92 merged 2 commits into
mindee:mainfrom
RavSinghChandan:download-interrupted-partial-file
Oct 31, 2026
Merged

felixdittrich92 merged 2 commits into
mindee:mainfrom
RavSinghChandan:download-interrupted-partial-file

Conversation

@RavSinghChandan

Copy link
Copy Markdown
Contributor

_urlretrieve wrote the download straight to its final path. When the download was interrupted (dropped connection, Ctrl+C), the truncated file stayed there, and the next download_from_url call reused it as is when there was no hash to check (a URL without a hash in its file name and no hash_prefix), so loading the file failed every time until it was deleted by hand.

The file is now downloaded to <name>.part and moved to its final path only once complete; the partial file is removed if the download fails

@felixdittrich92 felixdittrich92 self-assigned this Oct 30, 2026
@felixdittrich92 felixdittrich92 added this to the 1.2.0 milestone Oct 30, 2026
@felixdittrich92 felixdittrich92 added type: enhancement Improvement module: utils Related to doctr.utils ext: tests Related to tests folder labels Oct 30, 2026
@codecov

codecov Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.78%. Comparing base (50c2ef1) to head (0040d00).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #2204   +/-   ##
=======================================
  Coverage   97.77%   97.78%           
=======================================
  Files         170      170           
  Lines       10980    10993   +13     
=======================================
+ Hits        10736    10749   +13     
  Misses        244      244           
Flag Coverage Δ
unittests 97.78% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@felixdittrich92 felixdittrich92 left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks, downloading to a temporary file is a good fix: an interrupted download no longer leaves a truncated file that the next call would reuse. One issue with the fixed .part name, though:

Concurrent downloads of the same file now fail

Every call uses the same "{filename}.part". When two processes download the same file into the same cache at the same time (e.g. several web-server workers calling ocr_predictor(pretrained=True) on an empty cache), they write to the same .part file. The first one to finish renames it, and the second one's os.replace then fails:

FileNotFoundError: [Errno 2] No such file or directory: '.../model.pt.part' -> '.../model.pt'

I ran two processes downloading the same file into the same cache: on main both succeed, with this PR the second one crashes. A unique temporary name per call fixes it:

    tmp_path = Path(f"{filename}.{uuid.uuid4().hex}.part")

(plus import uuid at the top). With this change, both concurrent downloads succeed, the final file is intact, no temporary file is left behind, and your test still passes.

Please avoid tempfile.mkstemp for this: it creates the file with mode 0600, and os.replace would carry that onto the cached model, which can break a cache shared between users. The uuid name keeps the usual permissions (644).

Could you also add a test that a download doesn't touch another download in progress? This one fails with the current fixed name and passes with the uuid one

@RavSinghChandan

Copy link
Copy Markdown
Contributor Author

Thanks, you're right: both calls shared the same .part file. It now uses a name unique to each call
({filename}.{uuid4().hex}.part), which keeps the usual file permissions. I added
test_urlretrieve_concurrent_downloads: it runs a second download of the same file while the first one is in
progress. It fails with the fixed name (FileNotFoundError on os.replace) and passes with the uuid name.

@felixdittrich92 felixdittrich92 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks 👍🏼

@felixdittrich92
felixdittrich92 merged commit fd0f6cd into mindee:main Oct 31, 2026
57 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ext: tests Related to tests folder module: utils Related to doctr.utils type: enhancement Improvement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants