Course contentsShow
← Rust
Lesson 929 of 1,9107. Error Handling PatternsFree lesson

Error Handling in Distributed Systems

Handle network-level errors by distinguishing transient faults from permanent failures.

In this lesson, you will learn how to distinguish between transient network faults and permanent failures to prevent unnecessary system outages.

Distinguishing Failure Types

In distributed systems, the network is inherently unreliable. When an I/O operation fails, your code must decide whether to retry or propagate a fatal error. A "transient fault" (like a momentary packet loss or a busy load balancer) is temporary and often resolves itself with a retry. A "permanent failure" (like an invalid authentication token or a malformed request) will never succeed, no matter how many times you retry.

Treating these identically leads to either aggressive retry-storms that crash your downstream services or giving up too early on a fixable issue.

Implementing a Categorization Trait

We can leverage Rust’s powerful type system to enforce this distinction. By creating a custom trait, we can tag errors based on their recovery potential:

pub trait Transient {
    fn is_transient(&self) -> bool;
}

pub enum NetworkError {
    Timeout,            // Transient
    ConnectionRefused,  // Transient
    InvalidRequest,     // Permanent
}

impl Transient for NetworkError {
    fn is_transient(&self) -> bool {
        match self {
            NetworkError::Timeout | NetworkError::ConnectionRefused => true,
            NetworkError::InvalidRequest => false,
        }
    }
}

Applying the Logic

When you catch an error, use this categorization to determine the control flow:

async fn call_service(request: Request) -> Result<Response, NetworkError> {
    // ... logic that might fail
}

async fn execute_with_retry(request: Request) -> Result<Response, NetworkError> {
    let mut attempts = 0;
    loop {
        match call_service(request.clone()).await {
            Ok(val) => return Ok(val),
            Err(e) if e.is_transient() && attempts < 3 => {
                attempts += 1;
                // Exponential backoff logic would go here
            }
            Err(e) => return Err(e), // Permanent error or retry exhausted
        }
    }
}

By explicitly classifying errors, you ensure that your infrastructure remains resilient against "flaky" network conditions while failing fast on logical errors that require human or programmatic intervention.

Key Takeaway: Distinguish errors by their recoverability; only retry faults that satisfy a transient property to avoid unnecessary load and latency in your distributed system.