Skip to content

Retry budget resets to zero on every dispatch — defeated across process restarts #427

Description

@forkwright

Finding

When try_dispatch_next builds an ActiveEntry for a dequeued item it unconditionally sets retry_count: 0. The download_queue.retry_count column is incremented in the DB on each failure, but that persisted value is never read back when a recovered item is re-dispatched. row_to_queue_item reconstructs a QueueItem from a DB row, yet QueueItem carries no retry_count field, so the stored retry count is silently dropped. After a restart, every previously-retried item begins again with a fresh budget of 0, as if it had never been attempted.

Evidence

crates/syntaxis/src/lib.rs:338:

retry_count: 0,

(in the ActiveEntry construction inside try_dispatch_next)

crates/syntaxis/src/recovery.rs:31-46: row_to_queue_item builds a QueueItem from a QueueRow, but QueueItem has no retry_count field, so QueueRow::retry_count is never carried forward.

Why this matters

A permanently-failing download — a deleted or corrupted source that keeps returning transient-looking errors — is meant to exhaust its retry budget and stop. Because the budget resets to 0 on every dispatch after a restart, such an item retries forever on any host that restarts regularly (a phone reboots often). It permanently consumes a slot, repeatedly hits the network, and burns tracker credits. Under counter-surveillance, that is a recurring, predictable, externally-observable retry signature against the same dead endpoint — exactly the kind of stable beacon that aids traffic-pattern fingerprinting, plus a steady drain on battery and metered/scarce bandwidth.

Desired correction

Add retry_count: u32 to QueueItem. Populate it from QueueRow::retry_count (clamping to u32) in row_to_queue_item, and pass it through to ActiveEntry in try_dispatch_next instead of the hardcoded 0.

Done when: a recovered item with N prior retry attempts fails permanently after max_retries - N further attempts, not after max_retries.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions