Fix http_get_file deleting correctly downloaded files (gzip, chunked, resumed 206) - #1802
Open
Linxiushen wants to merge 1 commit into
Open
Linxiushen wants to merge 1 commit into
Linxiushen wants to merge 1 commit into
Conversation
http_get_file() compared the response's Content-Length with the number of bytes written to disk and, on mismatch, deleted the file and raised FileDownloadError. Content-Length does not describe the on-disk size in three common cases: it is absent for chunked responses (total is None), it counts the encoded bytes when the server applies Content-Encoding such as gzip while requests hands back the decoded stream, and it excludes the bytes already on disk when a resumed request is answered with 206. In all three a complete, byte-exact download was deleted and reported as incomplete. requests asks for gzip by default, so any origin with compression enabled triggers it; the video preprocessors and the action-detection / PROST / CLIP-video models call http_get_file() for arbitrary URLs. Only compare against an expected on-disk size when one can be established (identity encoding with Content-Length, or the complete length from a 206 Content-Range), and add a local http.server based test module covering gzip, chunked, resume, a 206 from the wrong offset and a truncated body.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
modelscope/hub/file_download.py::http_get_file()finishes withwhere
totalis the response'sContent-Length. That header does not describe the size of the file written to disk in three common cases, and in each of them a complete, byte-exact download is deleted and reported as incomplete:masterContent-Encoding: gzip(or deflate)Content-Lengthcounts the compressed bytes;requestshandsiter_contentthe decoded streamFileDownloadErrorContent-LengthtotalisNone,None != intis always trueFileDownloadError206 Partial ContentContent-Lengthcovers only the remaining range,downloaded_lengthis the whole fileFileDownloadErrorrequestssendsAccept-Encoding: gzip, deflateby default, so any origin with compression enabled hits the first case with no special configuration. The third case needs a download over one chunk (1 MB) that gets interrupted once — i.e. large files on a flaky network, which is exactly when resume matters.http_get_file()is used for arbitrary URLs bypreprocessors/video.py,models/cv/action_detection/action_detection_onnx.py,models/multi_modal/prost/models/prost_model.py(uncaught → pipeline crashes) andmodels/multi_modal/mmr/models/clip_for_mm_video_embedding.py(caught → returns an all-zero embedding).Fix
Only compare against an expected on-disk size when one can be established: identity encoding with a
Content-Length→ that length;206→ the complete length parsed fromContent-Range: bytes s-e/complete; chunked or non-identity encoding → skip the comparison. Skipping does not lose the truncation check: a response shorter than its declaredContent-Lengthalready raisesIncompleteReadinsideiter_content(urllib3 enforces it), which is what triggers the retry loop today.The
206handling deliberately usesContent-Range's complete length rather thandownloaded + Content-Length: a server that answers206but restarts from byte 0 would otherwise produce a corrupt file that passes the check. That case is covered by a negative test.Tests
tests/hub/test_http_get_file.py— five cases against a localhttp.server, no network or credentials, ~3 s:Content-Length→ file content equals the payload206→ file content equals the payload, more than one request made206restarting from the wrong offset → still rejected (FileDownloadError), file removedOn
masterthe first three error out (the file is deleted), the two negative cases pass; with this change all five pass. Additionally, 14 well-formed identity responses (0, 1, 4095, 1 MB−1, 1 MB, 1 MB+1, 3 MB+7 bytes, with and without an explicitContent-Encoding: identity) produce byte-identical results and hashes before and after the change.flake8 / yapf clean; isort applied to the new test file (
file_download.py's existing import block already fails the repo's isort settings onmaster, left untouched).Note: I could not run the full test suite locally because the installed
modelscope_hubwheel is older than the repo's pin;tests/has no other coverage ofhttp_get_file, and the change stays inside that function plus one private helper.Written with Claude Code (AI-assisted) and submitted under the account owner's authorization.