rwf2/Rocket · warning · io::Error
BrokenPipe
BrokenPipe
Error message
I/O driver terminated
What it means
Internal runtime error from Rocket's cancellable I/O (core/lib/src/listener/cancellable.rs): after the async I/O driver (tokio reactor) has terminated during shutdown, any further poll of connection I/O returns io::ErrorKind::BrokenPipe with message 'I/O driver terminated' (the gone() constructor). It is the definitive 'this runtime is gone' signal emitted once shutdown has progressed past the grace stage and resources are being torn down.
Source
Thrown at core/lib/src/listener/cancellable.rs:50
pub trait CancellableExt: Sized {
fn cancellable(self, stages: Stages) -> Cancellable<Self> {
Cancellable {
io: Some(self),
state: State::Active,
stages,
}
}
}
impl<T> CancellableExt for T { }
fn time_out() -> io::Error {
io::Error::new(io::ErrorKind::TimedOut, "shutdown grace period elapsed")
}
fn gone() -> io::Error {
io::Error::new(io::ErrorKind::BrokenPipe, "I/O driver terminated")
}
impl<I: AsyncCancel> Cancellable<I> {
pub fn inner(&self) -> Option<&I> {
self.io.as_ref()
}
}
pub trait AsyncCancel {
fn poll_cancel(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>>;
}
impl<T: AsyncWrite> AsyncCancel for T {
fn poll_cancel(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
<T as AsyncWrite>::poll_shutdown(self, cx)
}
}
View on GitHub (pinned to 3a54d079ae)
Solutions
- Accept it as part of shutdown: ensure connection-scoped tasks are tied to request/shutdown lifetimes so they stop when shutdown begins
- Use graceful shutdown signals (Rocket.toml [shutdown] with adequate grace) so connections close before the driver terminates
- In custom runtimes, keep the runtime alive until all connection tasks finish (JoinHandle them before dropping)
- Filter this message from production alerting — it is expected at process exit
Example fix
// before: detached task outlives the runtime
tokio::spawn(async move { loop { socket.read(&mut b).await; } });
// after: tie the task to shutdown
let mut shutdown = rocket.shutdown().clone();
tokio::spawn(async move {
tokio::select! {
_ = shutdown.notified() => {},
_ = socket.read(&mut b) => {},
}
}); Defensive patterns
Strategy: try-catch
Try / catch
// treat BrokenPipe 'I/O driver terminated' as end-of-life, not an error to propagate
match conn_io.poll() {
Err(e) if e.kind() == io::ErrorKind::BrokenPipe
&& e.to_string().contains("driver terminated") => { info_!("runtime gone, stopping"); return; }
other => other,
} Prevention
- Select connection loops against rocket.shutdown().notified() so tasks exit before the driver drops
- Never spawn detached I/O tasks that outlive the #[launch] runtime
- Suppress alerting on this exact message — it only occurs during teardown
When it happens
Trigger: Server is shutting down; grace expired; the tokio I/O driver is dropped; code still holding connection futures (e.g. a task blocked on read/write) polls them and receives this error. Also seen when a Rocket server future is dropped while connection tasks are still running.
Common situations: Tasks spawned per-connection that outlive the #[launch] runtime (e.g. manually spawned tasks holding request I/O); dropping the Rocket server inside a custom multi-runtime setup; shutdown-time log noise from in-flight requests.
Related errors
AI-assisted analysis of rwf2/Rocket@3a54d079ae (2026-08-16).
Data as JSON: /api/errors/bb4f7b46dbbb31e8.
Report an issue: GitHub.