Course contentsShow
← Rust
Lesson 1178 of 1,91010. Concurrency PrimitivesFree lesson

Async Error Propagation Architectures

You will design unified error handling that spans multiple task boundaries.

In this lesson, you will learn how to design robust error propagation strategies that maintain context as errors migrate across asynchronous task boundaries.

The Challenge of Distributed Faults

In synchronous code, errors propagate naturally up the call stack. In an async architecture, however, a task might spawn several sub-tasks across different threads or runtimes. When a child task fails, the "parent" that spawned it might be busy awaiting a different future or may have already been cancelled. Simply returning a Result is often insufficient because the failure context (the why) gets lost once the task handle is dropped or the JoinHandle returns an error.

Implementing Unified Propagation

To solve this, we move away from letting tasks "fail silently" and toward an architecture where error states are unified via a shared channel or an error-aware result wrapper.

Consider a pattern where your tasks return a domain-specific Error enum that includes the TaskID or a trace ID. By using a mpsc (multi-producer, single-consumer) channel, child tasks can report fatal errors back to a supervisor task without needing to wait for a join:

enum TaskError {
    Critical(String),
    Recoverable(u32),
}

async fn worker(tx: mpsc::Sender<TaskError>) {
    if let Err(e) = perform_work().await {
        let _ = tx.send(TaskError::Critical(e.to_string())).await;
    }
}

By centralizing error reporting in a supervisor, you can implement a "Circuit Breaker" pattern. If the supervisor observes an error frequency exceeding a threshold, it can signal cancellation to all other sibling tasks using CancellationToken patterns. This prevents the "cascading failure" syndrome where a single failing task consumes resources while the system remains unaware of the degradation.

Designing for Context

When propagating errors, ensure your error types implement Send + Sync so they can safely cross the await points between threads. Avoid using generic Box<dyn Error> for cross-task boundaries; instead, use descriptive enums that allow the supervisor to decide whether to retry, log, or terminate the entire system architecture. This turns "unhandled task failure" into a managed lifecycle event.

Key Takeaway: Unified async error propagation requires moving error reporting from the result of a JoinHandle into a dedicated monitoring channel that allows supervisors to orchestrate system-wide responses to task failures.