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.
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.
Appendix: 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)