Skip to content

No regression test that a retried download is re-dispatched after backoff #428

Description

@forkwright

Finding

The retry path in on_download_failed spawns a detached task that inserts the retry item back into the queue only after a backoff sleep, while try_dispatch_next() runs synchronously before that sleep ever completes. Nothing re-dispatches the item once it lands in the queue, and syntaxis has zero tests (lib.rs contains no #[cfg(test)] block), so the resulting stuck-queue behavior has no coverage.

Evidence

crates/syntaxis/src/lib.rs:287 — the retry insert is deferred behind the backoff sleep, decoupled from any dispatch trigger:

tokio::spawn(
    async move {
        tokio::time::sleep(tokio::time::Duration::from_secs(backoff)).await;
        inner.lock().await.queue.insert(queue_item);
    }
    .instrument(span),
);

crates/syntaxis/src/lib.rs:298 — dispatch is attempted once, immediately, before the deferred insert has happened:

self.try_dispatch_next().await;

grep -c '#\[cfg(test)\]\|#\[tokio::test\]\|#\[test\]' crates/syntaxis/src/lib.rs returns 0: there is no test module in the crate.

Why this matters

The retry mechanism is the recovery path for transient indexer and network failures — exactly the conditions that dominate in a hostile or intermittently-connected environment. With no dispatch trigger fired after the backoff insert, a retried item can sit in the queue indefinitely while emitting no error, so a user-requested download silently never completes. Because there is no test exercising this path, such a regression passes CI undetected and only surfaces as a quiet, hard-to-attribute hang in the field.

Desired correction

Add an integration test for syntaxis using an in-memory SQLite store and a mock engine that injects one transient failure. Drive time deterministically with tokio::time::pause / advance so the backoff elapses, then assert the item is dispatched a second time.

Done when: a test asserts that after a transient failure plus backoff, engine.start_download is invoked a second time for the retried item.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions