Conversation
lstein
left a comment
There was a problem hiding this comment.
Thanks for picking up #9480. The fix works for the path the issue describes, but it breaks one existing behavior, so I'm requesting one change before merge. Reviewed at 19ef9e9.
What works
- The #9480 path is fixed. A restored install is resumed and its first file gets a 416 before any download-started signal. It now goes PAUSED with its parts visible,
restart_failedre-queues them, and the PAUSED marker keeps each file'setag/canonical_url/download_pathinstead of savingfiles: []. - The regression test is meaningful. It fails with the new line removed and passes with it. The full
test_model_install.pypasses at the PR head. - No new thread-safety risk.
_download_started_callbackalready makes this exact assignment, so the install job and the multifile job already shared the same parts set. Nothing adds or removes parts aftermultifile_download()builds it.
Blocker: resuming no longer warns when a download restarted from scratch
_resume_remote_download (model_install_default.py:314-323) checks the job's current parts. For any part whose .downloading file has disappeared, it sets resume_from_scratch = True and resume_message = "Partial file missing. ...". It then calls _enqueue_remote_download. With this PR, that call replaces job.download_parts with brand-new parts before the resume route returns. The route returns job, and the UI decides whether to warn with hasRestartedFromScratch(job) on that response (ModelInstallQueueItem.tsx ~L194), so it no longer sees the flag.
The download queue has its own fallback (download_default.py:455), but it only fires when the part has an etag or last_modified (had_resume_metadata). For a job paused during the current session, job._resume_metadata is None, so the fresh parts have neither and the fallback never fires. The user sees "Download resumed" instead of the "restarted from scratch" warning, and lines 314-323 now write to parts that are thrown away immediately.
How to trigger it:
- Start a URL/HF install and let a file download partially (the started callback fills in
job.download_parts). - Pause the install.
- Delete that file's
.downloadingfile from the install tmpdir. - Click Resume. The response carries the fresh parts with no
resume_from_scratch, so the UI shows the success toast.
I confirmed this with a throwaway test. On this branch it fails; with the new line removed it passes:
def test_resume_reports_restart_from_scratch(mm2_installer, tmp_path, monkeypatch):
source = URLModelSource(url=Url("https://example.com/model.safetensors"))
old = DownloadJob(source=source.url, dest=tmp_path / "model.safetensors")
old.bytes = 4
old.download_path = tmp_path / "model.safetensors" # no .downloading on disk
job = ModelInstallJob(id=4242, source=source, config_in=ModelRecordChanges(), local_path=tmp_path)
job._install_tmpdir = tmp_path
job.download_parts = {old}
job.status = InstallStatus.PAUSED
rf = RemoteModelFile(url=source.url, path=Path("model.safetensors"), size=8)
monkeypatch.setattr(mm2_installer, "_remote_files_from_source", lambda _: ([rf], None))
monkeypatch.setattr(mm2_installer._download_queue, "submit_multifile_download", MagicMock())
mm2_installer.resume_job(job)
assert any(p["resume_from_scratch"] for p in job.model_dump(mode="json")["download_parts"])Suggested fix: in _resume_remote_download, collect the from-scratch flags from the old parts by str(part.source). After _enqueue_remote_download returns, copy resume_from_scratch and resume_message onto the matching new parts in job.download_parts. Please add the test above, or a similar one.
Non-blocking
- A restored install that was already PAUSED still can't be restarted directly. If the app quits after the 416 pause, the marker says
paused. On the next start,_restore_incomplete_installsstops atif job.paused: continuebefore anything is queued, sodownload_partsstays empty.restart_failedstill does nothing, and the UI hides Restart because it only checks the parts. Resume → 416 again → Restart works around it. Fillingdownload_partsfrom the marker'sfilesmetadata on restore, or lettingrestart_faileduse_resume_metadata, would close this. #9480's second suggestion (never overwrite a marker'sfileswith[]) is also still open in general, though I found no UI path that reaches it today. - The regression test skips the restore path. It calls
_enqueue_remote_downloaddirectly on a fresh job and sets theetagon the part by hand. It never exercises_restore_incomplete_installs→_resume_remote_downloadwith a real marker's resume data, which is the scenario in the issue. A variant that writes a marker, restores it, and then does the 416 pause and restart would pin the actual bug.
Summary
Fix restored installs that receive a non-resumable response before their first download-started signal.
resume_requiredstate visible to the API and available to the install marker and restart pathRelated Issues / Discussions
Closes #9480
QA Instructions
The focused regression passes, the full model-install test file passes 36 tests, and the related download-queue file passes all 36 tests when split between its mocked private-address cases and its four default-policy SSRF refusal cases. The split is needed locally because the sandbox maps public test hostnames into the reserved
198.18.0.0/15range.Merge Plan
No special merge sequencing is required.
Checklist