Skip to content

Retry re-enqueue after backoff never wakes the dispatcher #394

Description

@forkwright

Finding

A transient download failure schedules a retry via tokio::spawn: the task sleeps for the backoff duration, then inserts the retry item back into the queue. The dispatch call that should pick the item up (try_dispatch_next) runs at line 298 — synchronously, before the spawned task's sleep completes — so the queue is empty at dispatch time. When the sleep wakes and the item is inserted at line 290, nothing calls try_dispatch_next. The retry item sits in the queue indefinitely until some unrelated download completion or failure happens to fire an event.

Evidence

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

tokio::spawn(async move {
    tokio::time::sleep(tokio::time::Duration::from_secs(backoff)).await;
    inner.lock().await.queue.insert(queue_item); // no dispatch_next here
});
// ...
self.try_dispatch_next().await; // line 298 — runs before sleep wakes

The dispatch at line 298 observes the pre-insert (empty) queue; the post-sleep insert at line 290 has no following dispatch.

Why this matters

In a quiescent acquisition pipeline — the common steady state on a phone where no other download is active — a retried item is never re-dispatched. The retry path is silently dead: items that report as pending make no progress, so any content fetch that hit one transient error stalls until the device happens to trigger another unrelated event. On a counter-surveillance handset, content the operator is relying on (updates, payloads, mirrors) silently fails to arrive with no error surfaced, and the failure is invisible until manually inspected.

Desired correction

Drive a dispatch after the re-enqueue: call try_dispatch_next inside the spawned task immediately after queue.insert, or replace the sleep-then-insert pattern with a Notify/channel signal that wakes the dispatcher when a backoff expires. The dispatch must observe the queue in its post-insert state.

Done when: a unit test confirms a retried item is dispatched after its backoff elapses with no other downloads active.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions