Skip to content

Inner Result from audio and status tasks silently discarded — only panics are logged #388

Description

@forkwright

Finding

tokio::spawn returns a JoinHandle<Result<(), RenderError>> for both audio_task and status_task. The select! arms pattern-match the JoinHandle output with if let Err(e) = result, which matches only JoinError (a task panic). When receive_audio or send_status_reports returns a normal Err(RenderError), the handle resolves to Ok(Err(RenderError)). The outer if let Err(e) does not match, so the RenderError is dropped: no log line is emitted and connect_and_run falls through to Ok(()).

Evidence

crates/archon/src/render/runner.rs:258-262 — audio arm matches only the panic case; the warn text confirms intent:

result = audio_task => {
    if let Err(e) = result {
        warn!(error = %e, "audio task panicked");
    }
}

crates/archon/src/render/runner.rs:263-267 — identical pattern for the status task:

result = status_task => {
    if let Err(e) = result {
        warn!(error = %e, "status task panicked");
    }
}

The Ok(Err(RenderError)) arm has no handler in either branch, and the function returns Ok(()) at crates/archon/src/render/runner.rs:271.

Why this matters

A real audio decode error, a QUIC stream error, or a DSP failure inside receive_audio (and any failure inside send_status_reports) produces no log line and does not surface as an error from connect_and_run. The caller in run_renderer_loop cannot distinguish a clean connection close from a faulted task, so the exponential-backoff reconnect path is never taken — the renderer goes dark silently. On a counter-surveillance device this destroys observability into the audio path: an operator has no signal that the renderer failed (or was forced to fail by an adversary tampering with the stream), and the channel will not self-heal via reconnect.

Desired correction

Unwrap the inner result explicitly in both arms, e.g.:

result = audio_task => match result {
    Ok(Ok(())) => {}
    Ok(Err(e)) => { warn!(error = %e, "audio task failed"); /* propagate to trigger reconnect */ }
    Err(e) => warn!(error = %e, "audio task panicked"),
}

Apply the same three-way match to status_task, and propagate the inner Err out of connect_and_run so the reconnect loop engages.

Done when: a simulated audio decode error in receive_audio produces a warn! log distinct from the panic message and causes connect_and_run to return Err, exercising the reconnect-backoff path in run_renderer_loop.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions