# How to Manage Connection Closure, Errors, and Graceful Shutdowns in iroh Applications

> Master graceful shutdown, connection closure, and error handling in iroh applications. Learn to manage cancellations and task termination for robust system design.

- Repository: [number zero/iroh](https://github.com/n0-computer/iroh)
- Tags: how-to-guide
- Published: 2026-06-22

---

**Graceful shutdown in iroh is orchestrated through a hierarchical cancellation token system stored in `ShutdownState`, where `Router::shutdown()` aborts new connections, signals running tasks via `CancellationToken`, runs protocol handler cleanups, and awaits task termination before finalizing the runtime.**

Production applications built on n0-computer/iroh require deterministic shutdown mechanisms to prevent data loss and resource leaks when connections close or errors occur. Unlike abrupt termination, iroh's shutdown model propagates cancellation signals through every component—from sockets to protocol handlers—ensuring that managing connection closure and graceful shutdowns happens safely without dangling tasks. This architecture converts shutdown-related errors into standard `Result` values rather than panics, giving developers full control over error handling during the connection lifecycle.

## The iroh Shutdown Architecture

At the core of iroh's graceful shutdown is the **`ShutdownState`** struct defined in [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs) (lines 276-286). This state object holds a hierarchy of **cancellation tokens** that coordinate termination across the entire system.

The token hierarchy exposes two primary cancellation points:

- **`at_close_start`** – Cancels when the router begins its shutdown sequence
- **`at_endpoint_closed`** – Cancels after the endpoint has fully closed

Components throughout the codebase hold child tokens derived from these parents. When `Router::shutdown()` is invoked, the cancellation propagates through the socket layer, transport implementations, and remote maps, triggering immediate but orderly cleanup in every watching task.

## The Six-Stage Shutdown Sequence

When you invoke `Router::shutdown().await` as implemented in [`iroh/src/protocol.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/protocol.rs) (lines 429-440), the following deterministic sequence executes:

1. **Abort new connections** – The router stops accepting inbound connections and ceases creating new outbound ones immediately.

2. **Signal all running tasks** – The internal `CancellationToken` hierarchy is cancelled. Every task holding a child token (such as those in `RemoteMap` at [`iroh/src/socket/remote_map.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/remote_map.rs) lines 212-224) detects this signal and begins its own cleanup.

3. **Run protocol-handler shutdowns** – The router calls `ProtocolHandler::shutdown` for every registered protocol, as defined at [`iroh/src/protocol.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/protocol.rs) lines 397-416. Each handler closes its own streams gracefully.

4. **Wait for task termination** – The router awaits completion of all spawned tasks with a short timeout. If a task does not finish in time, the system logs a warning: `warn!("unexpected error in task shutdown…")`.

5. **Finalize runtime** – After all tasks complete or are forced to stop, the runtime itself shuts down via `self.runtime.shutdown().await` in [`iroh/src/runtime.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/runtime.rs).

6. **Mark the router as closed** – A flag (`self.shutdown.closed`) is set so future `is_shutdown()` checks return `true`, preventing new operations on the terminated router.

## Handling Connection Closure Errors

Errors that occur while a connection is being closed—such as a write after `close()` has been called—are **not fatal** to the process. Instead, these errors are converted into normal `Result` values and propagated back to the caller.

This design allows your application to inspect connection-closure errors just like any other I/O error without risking panic or undefined behavior. The error handling integrates seamlessly with Rust's `?` operator, enabling you to log, retry, or ignore shutdown-related failures as your application logic requires.

## Implementation Examples

### Standard Graceful Shutdown

The most common pattern for shutting down an iroh router:

```rust
let router = endpoint.router().clone();          // obtain the Router
router.shutdown().await?;                       // ← graceful shutdown
// All connections are closed cleanly; any I/O errors are returned as `Result`s.

```

### Handling Write Errors During Shutdown

When connections close while you're still writing, handle the error as a standard `Result`:

```rust
let conn = endpoint.connect(addr, b"my-alpn").await?;
let mut stream = conn.open_uni().await?;
if let Err(e) = stream.write_all(b"payload").await {
    // The write failed – maybe the peer already closed the connection.
    eprintln!("Write failed during shutdown: {}", e);
    // The error is just propagated; the router can still be shut down.
}

```

### Manual Token Cancellation (Advanced)

For scenarios requiring fine-grained control over shutdown timing, access the underlying tokens:

```rust
use iroh::socket::ShutdownState;
let shutdown = router.shutdown_state();          // expose the token set
shutdown.at_close_start.cancel();                // manually trigger early shutdown

```

### Test Harness Pattern

The iroh test suite demonstrates validation of shutdown behavior in [`iroh/src/test_utils/test_transport.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/test_utils/test_transport.rs) (lines 502-618):

```rust
// Validate shutdown completes without hanging
router.shutdown().await.map_err(|e| {
    eprintln!("Shutdown error: {}", e);
    e
})?;

```

## Key Source Files and Components

Understanding the following source locations is essential for debugging shutdown behavior:

- **[`iroh/src/protocol.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/protocol.rs)** – Contains `Router::shutdown` (lines 429-440) and `ProtocolHandler::shutdown` trait definition (lines 397-416)
- **[`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs)** – Defines `ShutdownState` struct and cancellation tokens (lines 276-286); implements the central I/O loop that reacts to shutdown signals (lines 714-740)
- **[`iroh/src/runtime.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/runtime.rs)** – Implements runtime-level shutdown that stops the async executor
- **[`iroh/src/socket/remote_map.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/remote_map.rs)** – Demonstrates token-aware graceful termination in the `RemoteMap` component (lines 212-224)

## Summary

- **Graceful shutdown** in iroh is driven by a `ShutdownState` containing hierarchical cancellation tokens (`at_close_start`, `at_endpoint_closed`).
- **`Router::shutdown()`** executes a six-stage sequence that stops new connections, signals tasks, runs protocol cleanups, awaits termination, finalizes the runtime, and marks the router closed.
- **Connection-closure errors** become standard `Result` values rather than fatal crashes, allowing normal error handling via `match` or the `?` operator.
- **Protocol handlers** receive explicit shutdown hooks via `ProtocolHandler::shutdown` to close their own streams gracefully.
- All shutdown coordination is implemented in [`iroh/src/protocol.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/protocol.rs) and [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs), with task-level examples in [`iroh/src/socket/remote_map.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/remote_map.rs).

## Frequently Asked Questions

### What happens if a protocol handler doesn't shut down in time?

The router waits for tasks with a timeout during stage four of the shutdown sequence. If a protocol handler or any spawned task fails to complete within this window, iroh logs a warning message (`warn!("unexpected error in task shutdown…")`) and proceeds to force-stop the remaining tasks before finalizing the runtime.

### Are connection errors during shutdown fatal to the application?

No. Errors that occur while closing connections—such as write failures after a peer disconnects—are converted into normal `Result` values and propagated to the caller. According to the iroh source code in [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs), these errors are treated as standard I/O errors and do not panic the router or terminate the process.

### How can I manually trigger shutdown signals without calling `Router::shutdown()`?

You can access the `ShutdownState` directly and call `cancel()` on the specific token you need, such as `shutdown.at_close_start.cancel()`. This pattern appears in [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs) (lines 276-286) but is rarely needed in standard application code; it exists primarily for testing and advanced coordination scenarios.

### What is the difference between `at_close_start` and `at_endpoint_closed`?

The `at_close_start` token cancels immediately when `Router::shutdown()` is called, signaling components to stop accepting new work. The `at_endpoint_closed` token cancels later in the sequence, after the underlying endpoint has fully closed and all connections have terminated. This two-phase approach allows for ordered cleanup where protocols close streams first (responding to `at_close_start`) before the final transport teardown (triggered by `at_endpoint_closed`).