Skip to content

Missing Seeding→Failed state transition leaves seeding-phase errors unrecoverable #453

Description

@forkwright

Finding

The can_transition_to match in DownloadState allows Seeding → SeedPolicySatisfied but has no Seeding → Failed arm. If a torrent in the Seeding state encounters a runtime error (the librqbit session is lost, the peer disconnects permanently, or disk becomes unavailable), the caller cannot legally transition the entry to Failed. Any call to entry.transition_to(DownloadState::Failed) from Seeding returns Err(InvalidStateTransition). The entry is then permanently stuck in Seeding with no path to Deleted or any terminal state.

Evidence

crates/ergasia/src/state.rs:30-38 — the allowed transitions:

(Queued, Initializing) | (Initializing, Downloading) | (Initializing, Failed)
| (Downloading, Completed) | (Downloading, Failed) | (Completed, Seeding)
| (Seeding, SeedPolicySatisfied) | (SeedPolicySatisfied, Deleted) | (Queued, Failed)

There is no (Seeding, Failed) arm. The invalid-transition test at line 132 explicitly asserts Seeding → Downloading fails, but there is no corresponding Seeding → Failed in the valid set.

Why this matters

A download stuck in Seeding with no legal exit transition can never be cleaned up or retried. Any retry or housekeeping logic that calls transition_to(Failed) receives an error and cannot proceed, leaking the entry forever. On a counter-surveillance device, abandoned seeding entries that cannot be terminated keep the node advertising itself to the swarm and consuming bandwidth past the operator's intent, widening the network-observable footprint. A similar gap exists for Completed → Deleted (the path must always pass through Seeding), preventing manual deletion of a completed download that should skip the seeding phase.

Desired correction

Add (Seeding, Failed) to the transition set. Consider also (Completed, Deleted) to allow forced deletion. Update the valid-transitions test to cover these cases and the invalid-transitions test to document what remains forbidden. Done when: entry.transition_to(DownloadState::Failed) from Seeding returns Ok(()) and the unit test passes.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions