fix(downloads): orphan cleanup skipped live empty-queue snapshots, stranding deleted downloads and blocking re-grabs with 409#755
Open
t3chnaztea wants to merge 1 commit into
Conversation
…randing deleted downloads and blocking re-grabs with 409 Deleting a torrent directly in the client when it was the only active download left its Download record stranded forever, and every re-grab returned HTTP 409 "already active". RemoveOrphansAsync bailed whenever a snapshot returned 0 items while downloads were tracked, on the theory the client might be unreachable, but that condition is already fully excluded upstream by the UsedCachedSnapshot and IsUnavailable guards: the poller surfaces every timeout/cancel/error as a cached or unavailable snapshot, never as a live empty one, so a live empty queue genuinely means the items are gone. Removing the redundant guard lets cleanup proceed on live empty snapshots while preserving the Listenarrs#640 protections unchanged: unreachable clients are still skipped, ImportPending/Ready/ImportBlocked are never terminalized (only Queued/Downloading/Paused are considered), and the per-item 5-minute grace period still shields fresh adds. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
The bug
Delete a torrent directly in the download client while it is the only active download, and its
Downloadrecord is stranded forever: every re-grab of that audiobook returns HTTP 409 "A download for this audiobook is already active" with no way to recover except manual removal. Delete one of several torrents and it self-heals, which makes the bug look intermittent.Root cause
DownloadOrphanCleanupService.RemoveOrphansAsyncbailed out whenever the client snapshot returned 0 queue items while downloads were tracked, on the theory that the client might be temporarily unreachable. But by that point unreachability is already fully excluded by the two guards above it (UsedCachedSnapshotandIsUnavailable):DownloadClientQueuePollersurfaces every timeout/cancel/error path as a cached or unavailable snapshot (BuildFallbackQueueResult), never as a live one —snapshotState: "live"is produced only by the success path. So a live empty queue genuinely means the items are gone, and the guard's only real effect was to make the sole-torrent deletion permanently uncleanable.The fix
Remove the redundant guard so cleanup proceeds on live empty snapshots. The design cares from #640 are preserved unchanged:
Queued/Downloading/Pausedare ever terminalized —ImportPending/Ready/ImportBlockedremain untouched;Tests
RemovesOnlyTrackedDownloadWhenLiveSnapshotIsEmpty— the reported scenario: sole tracked download, live empty snapshot, past grace → removed.DoesNotRemoveWhenEmptySnapshotIsNotTrusted(Theory: cached / unavailable) — the Two cleanup paths never wired up: unbounded DownloadProcessingJobs; orphaned downloads never terminalized #640 protection stays intact.DoesNotRemoveRecentDownloadWhenLiveSnapshotIsEmpty— grace period respected.DoesNotRemoveIdlessDownloadWhenLiveSnapshotIsEmpty→RemovesIdlessDownloadWhenLiveSnapshotIsEmpty: the old test encoded the bug behavior.Full suite green: 1193 passed / 0 failed;
dotnet format --verify-no-changesclean.🤖 Generated with Claude Code