How to Gracefully Close iroh Endpoint and Connections

Call Endpoint::close().await to initiate graceful shutdown on the endpoint, and use Connection::close(error_code, reason) followed by Connection::closed().await to shut down individual QUIC connections cleanly.

When building peer-to-peer applications with the iroh networking stack, properly releasing resources is critical to prevent connection leaks and ensure all QUIC close frames are transmitted. This guide explains how to gracefully close iroh Endpoint and connections using the specific APIs exposed in iroh/src/endpoint.rs and iroh/src/endpoint/connection.rs.

Gracefully Closing Individual Connections

Connection objects expose two primary methods for graceful termination: close() to signal the remote peer, and closed() to await the final teardown.

Sending a Close Signal

The Connection::close method accepts a QUIC error code and an optional reason byte slice, forwarding the request to the underlying QUIC connection:

pub fn close(&self, error_code: VarInt, reason: &[u8]) {
    self.inner.quic_conn.close(error_code, reason);
}

Located in iroh/src/endpoint/connection.rs around line 185, this method initiates the QUIC close process without blocking.

Waiting for Confirmation

To block until the connection is fully terminated, await the closed() future:

pub async fn closed(&self) -> ConnectionError {
    self.inner
        .closed
        .await
        .expect("closed sender dropped")
}

This asynchronous method, found near line 170 in iroh/src/endpoint/connection.rs, resolves when the connection enters a terminal state, returning the ConnectionError that caused the closure.

Shutting Down the Endpoint

The Endpoint struct provides similar semantics for graceful shutdown at the listener level.

Initiating Endpoint Closure

Call Endpoint::close() to gracefully shutdown the endpoint. This method closes all outgoing connections and waits for ongoing connections to finish:

pub async fn close(&self) {
    // Closes all outgoing connections and waits for ongoing connections to finish
    self.inner.close_all_connections().await;
    self.inner.shutdown().await;
}

As implemented in iroh/src/endpoint.rs around line 1690, this async function ensures no new connections are accepted and signals existing ones to terminate.

Awaiting Full Termination

After calling close, you can await the closed() future to ensure all background tasks complete:

pub fn closed(&self) -> EndpointClosed {
    self.inner.closed()
}

This method returns an EndpointClosed future that resolves when the endpoint's internal resources are fully released.

Complete Shutdown Sequence

A robust shutdown follows this order:

  1. Close all active connections using connection.close(0u32.into(), b"shutting down")
  2. Await each connection's termination with connection.closed().await
  3. Call endpoint.close().await to stop accepting new connections
  4. Await endpoint.closed().await to ensure the socket and background tasks are cleaned up
  5. Drop the endpoint to release remaining resources

Implementation Details

The graceful shutdown logic resides in specific source files:

  • iroh/src/endpoint/connection.rs: Contains Connection::close (line ~185) and Connection::closed (line ~170)
  • iroh/src/endpoint.rs: Implements Endpoint::close (line ~1690) and Endpoint::closed
  • The close methods forward to the underlying QUIC implementation via self.inner.quic_conn.close()
  • The closed futures monitor internal channels that signal when the QUIC stack reports termination

Summary

  • Use Connection::close(error_code, reason) to send a QUIC close frame with a specific error code and optional reason string
  • Await Connection::closed() to block until the connection is fully terminated and retrieve the close reason
  • Call Endpoint::close().await to initiate graceful shutdown, which closes all connections and stops accepting new ones
  • Await Endpoint::closed() to ensure all background tasks and socket resources are released before dropping the endpoint

Frequently Asked Questions

What error code should I pass to Connection::close?

Pass 0u32.into() (converted to VarInt) for a normal, error-free shutdown. Non-zero codes indicate application-specific errors or abrupt termination.

Does Endpoint::close wait for all connections to close?

Yes. According to the implementation in iroh/src/endpoint.rs, the close method waits for ongoing connections to finish and closes all outgoing connections before returning.

Can I reuse an Endpoint after calling close?

No. The close method is a terminal operation. The endpoint will not be restarted; you must create a new Endpoint instance if you need to resume networking.

What happens if I don't await Connection::closed?

The connection will still close, but you won't receive confirmation of when the peer acknowledged the close or the final error state. Always await closed() in critical paths to ensure clean resource reclamation.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →