facebook/flow · error

Daemon::flush failed

Error message

Daemon::flush failed

What it means

Panics when flushing the daemon output channel's underlying stream fails. The comment in this port notes InChannel/OutChannel always wrap TcpStream, and std's TcpStream::flush is effectively a no-op that returns Ok, so in practice this site is only reachable through an OS-reported I/O error or a wrapped writer. The OCaml original raised on write/flush errors of the channel the same way.

Source

Thrown at rust_port/crates/flow_daemon/src/daemon.rs:70

pub fn from_channel<T: DeserializeOwned>(ic: &mut InChannel<T>, timeout: Option<Duration>) -> T {
    try_from_channel(ic, timeout).expect("Daemon::from_channel: bincode deserialize")
}

pub fn try_from_channel<T: DeserializeOwned>(
    ic: &mut InChannel<T>,
    timeout: Option<Duration>,
) -> Result<T, bincode::error::DecodeError> {
    ic.stream
        .set_read_timeout(timeout)
        .map_err(|e| bincode::error::DecodeError::Io {
            inner: e,
            additional: 0,
        })?;
    bincode::serde::decode_from_std_read(&mut ic.stream, bincode::config::legacy())
}

pub fn flush<T>(oc: &mut OutChannel<T>) {
    oc.stream.flush().expect("Daemon::flush failed");
}

// OCaml's `Unix.file_descr` is uniformly an int (or HANDLE on Windows),
// allowing it to refer to either a file or a socket. Rust has no such
// uniform type that is also cross-platform, so for `InChannel`/`OutChannel`
// (which are always TCP sockets in this port) we expose the underlying
// `TcpStream` directly. Callers that need an independent handle can call
// `try_clone()` on the returned reference -- this works on both Unix and
// Windows, unlike `BorrowedFd`/`nix::unistd::dup` which are Unix-only.
pub fn descr_of_in_channel<T>(ic: &InChannel<T>) -> &TcpStream {
    &ic.stream
}

pub fn descr_of_out_channel<T>(oc: &OutChannel<T>) -> &TcpStream {
    &oc.stream
}

pub fn into_out_writer<T>(oc: OutChannel<T>) -> Box<dyn std::io::Write + Send> {

View on GitHub (pinned to f88ac94bcf)

Solutions

  1. Check the peer/connection is still alive before flushing (take_error on the TcpStream) and reconnect if not
  2. Confirm nothing wraps or replaces the OutChannel stream type with a writer whose flush can fail
  3. Treat a flush panic as a symptom of the peer dying: restart the daemon and re-establish channels

Example fix

// before
daemon::flush(&mut oc);

// after — surface the pending socket error instead of panicking
if let Some(err) = oc.stream.take_error().ok().flatten() {
    return Err(anyhow::anyhow!("daemon socket error before flush: {err}"));
}
daemon::flush(&mut oc);
Defensive patterns

Strategy: validation

Validate before calling

// Surface a pending socket error before flushing
if let Some(err) = oc.stream.take_error().ok().flatten() {
    return Err(anyhow::anyhow!("daemon socket error: {err}"));
}
daemon::flush(&mut oc);

Try / catch

let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| daemon::flush(&mut oc)));
// A flush panic means the channel is dead: tear it down and reconnect.

Prevention

When it happens

Trigger: Calling daemon::flush on a channel whose socket already has a pending error reported by the OS (connection reset by peer, EPIPE); the peer closed the connection between the last write and the flush; a custom/wrapped writer with a real flush implementation that errors.

Common situations: Daemon process exits between message write and flush; connection torn down by a firewall or container proxy; extremely rare on plain TCP sockets since std flush does not transmit anything.

Related errors


AI-assisted analysis of facebook/flow@f88ac94bcf (2026-08-20). Data as JSON: /api/errors/8746e006d8874c4d. Report an issue: GitHub.