valeriansaliou/sonic · error · std::io::Error

BrokenPipe

Error message

BrokenPipe

What it means

Channel::send maps a failure on the internal oneshot/command channel (timing out after SEND_TIMEOUT or the receiver dropping) into an io::Error of kind BrokenPipe. It means the command could not be delivered to the channel worker — the connection is effectively dead.

Source

Thrown at client/src/channel.rs:156

                                if let Err(error) = send_res {
                                    // Only log an error, as this would happen if the receiver is dropped.
                                    log_error!("Could not send response: {error}");
                                }
                            }

                            Err(error) => {
                                if let Err(send_error) = tx.send(Err(error)) {
                                    // Only log an error, as this would happen if the receiver is dropped.
                                    log_error!("Could not send response: {send_error}");
                                }
                            }
                        }
                    }),
                },
                SEND_TIMEOUT,
            )
            .map_err(|error| {
                std::io::Error::new(std::io::ErrorKind::BrokenPipe, error.to_string())
            })?;
        self.poll_waker.wake()?;

        Ok(rx)
    }

    /// Sends a command asynchronous at the protocol level (e.g. using the
    /// `PENDING` + `EVENT` pattern).
    pub(crate) fn send_async<T: Send + 'static>(
        &self,
        command: Command,
        discriminant1: impl Into<Mode::Discriminant>,
        make_discriminant2: impl FnOnce(&str) -> Mode::Discriminant + Send + Sync + 'static,
        parse: impl Fn(&str) -> std::io::Result<T> + Send + 'static,
    ) -> std::io::Result<oneshot::Receiver<std::io::Result<T>>> {
        if command.len() > self.channel_info.buffer_size {
            return Err(std::io::Error::new(
                std::io::ErrorKind::InvalidInput,

View on GitHub (pinned to e6a72da6a5)

Solutions

  1. Reconnect the channel/client and retry the command
  2. Check server logs for why the channel thread closed (e.g. buffer overflow, protocol error)
  3. Verify the server is still running and reachable (health check)
  4. Reduce command cost or increase tolerance for slow commands so SEND_TIMEOUT is not hit

Example fix

// client
// before
let rx = channel.send(cmd, disc, parse)?; // panics/errs with BrokenPipe if connection died
// after
match channel.send(cmd, disc, parse) {
    Ok(rx) => ..., 
    Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => reconnect_and_retry(),
    Err(e) => return Err(e),
}
Defensive patterns

Strategy: retry

Validate before calling

// detect a dead channel before sending
if channel.is_closed() || last_send_failed {
    reconnect();
}

Try / catch

match channel.send(cmd, disc, parse) {
    Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => {
        reconnect();
        channel.send(cmd, disc, parse)?;
    }
    other => other?,
}

Prevention

When it happens

Trigger: send_buffered -> send: the oneshot::Sender::send + timeout(SEND_TIMEOUT) path returns Err (worker stopped, receiver dropped, or timeout elapsed), which is converted via map_err to ErrorKind::BrokenPipe.

Common situations: Server closed or reset the connection (see the server's 'closing channel' panic); long-blocking commands exceeding SEND_TIMEOUT; using a Channel object after the underlying connection was dropped.

Related errors


AI-assisted analysis of valeriansaliou/sonic@e6a72da6a5 (2026-09-01). Data as JSON: /api/errors/9e39bd2899135a0c. Report an issue: GitHub.