Skip to content

fix: stalled torrents wait in the client instead of being cancelled; find them by hash - #66

Merged
mikaeltarquin merged 1 commit into
mainfrom
fix/mam-freeze-cancelled
Oct 5, 2026
Merged

mikaeltarquin merged 1 commit into
mainfrom
fix/mam-freeze-cancelled

Conversation

@mikaeltarquin

Copy link
Copy Markdown
Owner

Why

After #65, a batch of MAM torrents went to Deluge. The ones within the unsatisfied limit finished downloading, yet every one of them showed as Cancelled in Shelfmark.

The cause is the stall check. It cancels any download with no progress for 5 minutes. A torrent sitting at 0% in the client (no peers yet, Deluge's own queue, or MAM's tracker refusing it while over the limit) was cancelled but left in the client. It finished there with nothing to import it, and the worker's "Cancelled" message overwrote the stall reason.

Retrying one also hit MAM's download freeze. Finding an existing torrent meant downloading the .torrent again to get its hash, and MAM refuses that during the freeze. So the retry wasn't recognized as already in Deluge and waited for the freeze.

What changes

  • Stalled torrents wait in the client instead of being cancelled. This applies to Deluge, qBittorrent, Transmission and rTorrent, which keep a torrent by its hash. The download frees its worker and waits on Activity → Queued ("Waiting in Deluge: no progress for 5 minutes…").
    • A watcher checks every 2 minutes. It returns the download to the workers once the client shows it progressing or finished.
    • It fails the download ("no longer in Deluge") if the torrent has been removed.
    • Cancelling it from Activity still works.
    • Stalls elsewhere are still cancelled as before.
  • Hash kept at hand-off. DownloadTask.torrent_client_hash is set when the torrent is added to (or found in) the client. It also fills retry_expected_hash when the release had none.
  • Find existing downloads by hash first. find_existing_download tries the known hash as a magnet before the URL, so no .torrent fetch is needed. It's used by the handler and by the slot check's "already in the client" test.
  • The client display-name map moves from mam_account to download.clients.client_label.
  • Changelog and troubleshooting docs updated.

Testing

  • New tests/download/test_wait_in_client.py (13 tests) covers:
    • parking instead of cancelling, while non-client stalls still cancel
    • release on progress or completion, failure when the torrent is gone, and dropping a cancelled wait
    • a retry finding its torrent by hash with no .torrent fetch
    • the URL fallback and when the hash is or isn't kept
  • pytest tests/download tests/prowlarr: 1216 passed. In the full suite the only failures are the 7 entrypoint/hardlink tests that also fail on a clean main in this container (they run as root).
  • Ruff, BasedPyright (backend and tests) and Vulture are clean.
  • Not run against a real torrent client.

Known limit

The history row's retry data is only saved when a download finishes or is cancelled. After a restart mid-download, a retry falls back to finding the torrent by URL, which MAM refuses during a freeze.

🤖 Generated with Claude Code

https://claude.ai/code/session_0184DMrDNkAEmGzPntj3ziqB


Generated by Claude Code

…find them by hash

A torrent with no progress for 5 minutes (no peers yet, queued in Deluge, or
MAM's tracker refusing it for now) was cancelled by the stall check but left
downloading in the client, so it finished there with nothing to import it and
showed as Cancelled.

- A stalled download that's in a hash-keyed torrent client (Deluge,
  qBittorrent, Transmission, rTorrent) gives up its worker and waits on
  Activity -> Queued; a watcher sends it back to the workers once the client
  shows it moving or finished, and fails it if it's no longer there.
- The client's hash is kept on the task when the torrent is handed off (and
  as the retry hash), and finding an existing download tries it first as a
  magnet: a retry no longer has to download the .torrent, which MAM refuses
  during its download freeze.
- Client display names move to the clients package for reuse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0184DMrDNkAEmGzPntj3ziqB
@mikaeltarquin
mikaeltarquin merged commit 6073deb into main Oct 5, 2026
14 of 21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants