Repository navigation
feat: re-download outdated model weights as a cancellable batch - #31
Merged
Merged
Conversation
model_manager.py stood at 753 lines against the 800-line module budget, so the next feature would have had to be squeezed in or would have pushed a compliant module over the ceiling. The bulk re-download of outdated weights (#29) needs the room. The module becomes a package with the layering it already had implicitly: _entries holds the row model and the cache lookups, _dialog the widget, _workers the three background workers, and _controller the protocol and the controller that wires them. The largest unit is 303 lines. __init__ re-exports the three names the two importers in the repository use, so no call site changes and no compatibility shim is needed: the UI tests monkeypatch Qt classes, not module globals. Code moved, nothing rewritten; a sorted-line comparison of the old module against the concatenated units differs only in import statements. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fetch() hard-coded the manager's own cancellation state, which in production is the preview job's and is false whenever no job runs. A dialog that downloads a weight could therefore pass a progress callback but had no way to stop what it started. It now takes an optional cancellation check and keeps the manager's when none is given, so every existing caller is unchanged. remove_obsolete_versions() takes an optional model id so one outdated weight can be removed once its replacement has been verified, instead of clearing the whole namespace before the first download. The catalog resolves the id, so the extra path segment is always a known model, and the symlink and directory guards are untouched. Growth of the frozen jobs/models/session.py from 432 to 439 lines was agreed with the maintainer before the baseline was updated, as the guardrails require. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The bulk action removed before #24 landed started up to 6.35 GiB of downloads from one unconfirmed click, survived closing the dialog and left shutdown blocked on the manager lock. It returns with the four things that were missing. A confirmation names the count and the size in the units the manager already prints, and says both that each old copy is deleted only once its replacement verifies and that the other files in the obsolete directory stay until Delete outdated. The batch owns a cancellation state that reaches the downloader, so Cancel, Esc and the title-bar X all stop it within one chunk; the dialog stays open and reports what it managed. Controller close() cancels, joins, releases the busy state and closes the dialog, so quitting the application no longer blocks behind a running fetch. The status line carries the model, its position in the batch and the transfer rate. The first failure stops the batch. Because each old weight is deleted only after its replacement is verified, what remains is truthful: the notice, the rows and the button describe exactly the models still to fetch, and one click resumes. That is why there is no failure aggregation and no second summary format. While a worker thread lives, the GUI thread reads nothing that takes the manager lock; rows refresh once at the end. The test fake raises when its active id is read during a fetch, and the batch test blocks on its second model so the slots that run between models are covered by that guard rather than racing past it. Closes #29 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
smb
force-pushed
the
feat/bulk-redownload-outdated
branch
from
September 4, 2026 10:40
1057fbe to
b7c1d43
Compare
Collaborator
Author
|
Reviewed by Fable against the design and the guardrails, with the three commits in hand. It found no lock-rule violation in the code, no repeat of the #27 thread-lifetime shape, no path that escapes the cache root, and no vacuous test. Five things were worth acting on and are in the branch now (the two feature commits were amended, so the branch was force-pushed with lease):
Three findings are deliberately left, and two of them are the design being wrong rather than the code:
|
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.
Closes #29
Summary
The bulk Re-download outdated removed before #24 landed comes back with the four things it was missing. Three commits: a behaviour-neutral package split, the session API change, the feature.
fetch()gains an optional cancellation check; the batch owns aCancellationStatethat reaches the downloader, so Cancel, Esc and the title-bar X stop it within one chunk (or the transport's 60 s stall bound). The dialog stays open and reports what it managed instead of hiding with work still running.ModelManagerController.close()cancels before it waits, so quitting no longer blocks behind a running fetch on the manager lock.Two decisions deviate from the issue's wish list and are recorded in its thread:
remove_obsolete_versions()takes an optional model id. Transient double disk is one weight, not the set, and the old weights stay valid for the previous build until replaced.Lock discipline. While a worker thread lives, the GUI thread reads nothing that takes the manager lock; rows refresh once at the end. The test fake enforces this by raising if its active id is read during a fetch, so every test in the file checks the rule rather than one test asserting it.
jobs/models/session.pygrows 432 to 439 lines, agreed with the maintainer before the baseline was updated.ui/model_manager.pybecame a package first (largest unit 464 lines) because the module stood at 753 against the 800-line budget.Review
Design by Fable against the analysis in the #24 thread, implementation by the Codex model from that design, then reviewed.
Test plan
uv run ruff check . && uv run mypy src && QT_QPA_PLATFORM=offscreen uv run pytest -q: 1618 passed, 4 skipped; 1604 before this branch.close()cancelling before waiting, a single download cancelled without a warning box, refusal while a job runs, and the fake's lock-rule assertion across all of them.🤖 Generated with Claude Code