valeriansaliou/sonic · warning · std::io::Error
BrokenPipe
Error message
BrokenPipe
What it means
In the generated blocking wrapper quit_blocking_, the oneshot recv_timeout(RECV_TIMEOUT) on the quit response is remapped to ErrorKind::BrokenPipe on timeout. A timeout here means the response never arrived because the channel's worker/dispatcher is gone. It indicates the channel died before the quit acknowledgement could be delivered.
Source
Thrown at client/src/util.rs:138
pass: impl AsRef<str>,
multiplexer: &$crate::SonicMultiplexer,
) -> std::io::Result<Self> {
SonicChannel::<self::Mode>::connect::<T>(addr, pass, multiplexer)
.map(|inner| Self { inner })
}
pub fn server_info(&self) -> &crate::events::ServerInfo {
&self.inner.server_info
}
pub fn channel_info(&self) -> &crate::events::ChannelInfo {
&self.inner.channel_info
}
fn quit_blocking_(&mut self) -> std::io::Result<()> {
self.quit()?
.recv_timeout($crate::RECV_TIMEOUT)
.map_err(|error| std::io::Error::new(std::io::ErrorKind::BrokenPipe, error))?
}
}
impl Drop for $low_level_ty {
#[inline]
fn drop(&mut self) {
if !self.inner.is_closed() {
$crate::logging::log_trace!(concat!("[Drop] Quitting ", stringify!($low_level_ty)));
self.quit_blocking_().unwrap_or_else(|error| crate::logging::log_error!("{error:?}"));
}
}
}
#[cfg(feature = "sync")]
type BlockingChannel = $blocking_ty;
#[doc = concat!("A blocking way to interact with a Sonic Channel in ", stringify!($mode), " mode.")]
#[doc = concat!("\n\nWhen in an asynchronous context (e.g. using `tokio`), use [`", stringify!($async_ty), "`] instead.")]View on GitHub (pinned to e6a72da6a5)
Solutions
- Treat BrokenPipe on quit as benign — the channel is already down; ignore or log it.
- Quit only once and guard against Drop racing manual quit.
- Reconnect if you still need a clean session teardown.
- Verify the runtime is not being torn down while quit's reply is pending.
Example fix
// before
channel.quit_blocking()?;
// after
if let Err(e) = channel.quit_blocking() {
if e.kind() != std::io::ErrorKind::BrokenPipe {
return Err(e);
} // already closed: fine
} Defensive patterns
Strategy: try-catch
Type guard
fn is_broken_pipe(e: &std::io::Error) -> bool {
e.kind() == std::io::ErrorKind::BrokenPipe
} Try / catch
if let Err(e) = channel.quit_blocking() {
if e.kind() != std::io::ErrorKind::BrokenPipe {
return Err(e);
}
// channel already dead: treat quit as done
} Prevention
- Call quit only once; let Drop handle the rest
- Ignore BrokenPipe on quit — it means the channel was already down
- Keep the runtime alive until the quit acknowledgement arrives
- Guard manual quit against Drop with a flag (Once/AtomicBool)
When it happens
Trigger: Calling the blocking quit variant when the connection task already exited (send succeeded but nobody responds), or the worker was killed between enqueue and reply; also RECV_TIMEOUT expiring due to extreme stalls.
Common situations: Double-quit or quit after a prior connection failure; Drop impls racing with manual quit; shutdown ordering issues in multithreaded apps.
Related errors
AI-assisted analysis of valeriansaliou/sonic@e6a72da6a5 (2026-09-01).
Data as JSON: /api/errors/d99abc73e60145ea.
Report an issue: GitHub.