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:
- Close all active connections using
connection.close(0u32.into(), b"shutting down") - Await each connection's termination with
connection.closed().await - Call
endpoint.close().awaitto stop accepting new connections - Await
endpoint.closed().awaitto ensure the socket and background tasks are cleaned up - Drop the endpoint to release remaining resources
Implementation Details
The graceful shutdown logic resides in specific source files:
iroh/src/endpoint/connection.rs: ContainsConnection::close(line ~185) andConnection::closed(line ~170)iroh/src/endpoint.rs: ImplementsEndpoint::close(line ~1690) andEndpoint::closed- The
closemethods forward to the underlying QUIC implementation viaself.inner.quic_conn.close() - The
closedfutures 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().awaitto 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →