Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Error Handling

Mosaik uses typed error enums specific to each subsystem, plus a close-reason system for QUIC connection-level codes. This chapter catalogs every public error type.

Network errors

network::Error — returned by NetworkBuilder::build():

VariantDescription
MissingNetworkIdNo network_id was set on the builder
Bind(BindError)Failed to bind the QUIC endpoint
InvalidAddress(InvalidSocketAddr)An address in addresses is invalid
DiscoveryConfig(ConfigBuilderError)Discovery config builder failed validation
StreamsConfig(ConfigBuilderError)Streams config builder failed validation
GroupsConfig(ConfigBuilderError)Groups config builder failed validation

Discovery errors

discovery::Error:

VariantDescription
InvalidSecretKey(PeerId, PeerId)Secret key does not match expected PeerId
DifferentNetwork { local_network, remote_network }Remote peer is on a different network
InvalidSignaturePeer entry signature verification failed
GossipJoin(ApiError)Failed to join gossip topic (iroh_gossip)
PeerIdChanged(PeerId, PeerId)Attempted to change the local peer’s ID
Link(LinkError)Transport-level link error
Other(Box<dyn Error>)Generic boxed error
CancelledOperation was cancelled

Command errors

groups::CommandError<M> — returned by execute(), execute_many(), feed(), feed_many():

VariantRecoverable?Description
Offline(Vec<M::Command>)YesNode is offline; carries the unsent commands for retry
NoCommandsNoEmpty command batch was submitted
GroupTerminatedNoThe group has been permanently closed

Recovering from Offline

match group.execute(MyCommand::Increment).await {
    Ok(result) => println!("committed: {result:?}"),
    Err(CommandError::Offline(commands)) => {
        // Wait for the group to come online, then retry
        group.when().online().await;
        for cmd in commands {
            group.execute(cmd).await?;
        }
    }
    Err(CommandError::GroupTerminated) => {
        // Permanent failure — stop trying
    }
    Err(CommandError::NoCommands) => unreachable!(),
}

Query errors

groups::QueryError<M> — returned by query():

VariantRecoverable?Description
Offline(M::Query)YesNode is offline; carries the unsent query
GroupTerminatedNoThe group has been permanently closed

Collection errors

collections::Error<T> — returned by collection write operations:

VariantRecoverable?Description
Offline(T)YesNode is offline; carries the value for retry
NetworkDownNoNetwork has shut down

The generic T contains the value that failed to be written, enabling retry without re-creating the data:

match map.insert("key".into(), 42).await {
    Ok(prev) => println!("previous: {prev:?}"),
    Err(collections::Error::Offline(value)) => {
        // value == 42, retry later
    }
    Err(collections::Error::NetworkDown) => {
        // permanent failure
    }
}

Producer errors

streams::producer::Error<D> — returned by Sink::send() and try_send():

VariantDescription
Closed(Option<D>)Producer has been closed
Full(D)Internal buffer is full (back-pressure)
Offline(D)No active consumers connected

All variants carry the datum back when possible, enabling retry.

Close reasons (QUIC application codes)

Mosaik uses typed close reasons for QUIC ApplicationClose codes. These are generated with the make_close_reason! macro and appear in connection close frames.

Reserved ranges

RangeOwner
0–199mosaik internal
200+Application-defined

Built-in close reasons

NameCodeDescription
Success200Protocol completed successfully
GracefulShutdown204Graceful shutdown
InvalidAlpn100Wrong ALPN protocol
DifferentNetwork101Peer on a different network
Cancelled102Operation cancelled
UnexpectedClose103Unexpected connection close
ProtocolViolation400Protocol message violation
UnknownPeer401Peer not found in discovery catalog

Group close reasons

NameCodeDescription
InvalidHandshake30,400Handshake decode error
GroupNotFound30,404Unknown group ID
InvalidProof30,405Invalid authentication proof
Timeout30,408Peer response timeout
AlreadyBonded30,429Duplicate bond between peers

Defining custom close reasons

use mosaik::network::make_close_reason;

// Code must be >= 200
make_close_reason!(MyAppError, 500, "Application-specific error");

Error design patterns

Temporary vs. permanent

Mosaik errors follow a consistent pattern:

  • Temporary errors carry the original data back (e.g., Offline(commands), Full(datum)) so you can retry without data loss.
  • Permanent errors (e.g., GroupTerminated, NetworkDown) indicate the resource is gone and retrying is pointless.

Matching on recoverability

use mosaik::collections::Error;

loop {
    match map.insert("key".into(), value.clone()).await {
        Ok(_) => break,
        Err(Error::Offline(_)) => {
            map.when().online().await;
            continue;
        }
        Err(Error::NetworkDown) => {
            panic!("network is gone");
        }
    }
}

The CloseReason trait

All close reason types implement:

trait CloseReason:
    Error + Into<ApplicationClose> + PartialEq<ApplicationClose>
    + Clone + Send + Sync + 'static
{}

This lets you match connection close frames against typed reasons:

if close_frame == InvalidProof {
    // handle proof failure
}