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
- Check the peer/connection is still alive before flushing (take_error on the TcpStream) and reconnect if not
- Confirm nothing wraps or replaces the OutChannel stream type with a writer whose flush can fail
- 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
- Treat any write/flush panic on the daemon channel as a dead connection and re-establish it
- Do not wrap OutChannel's stream in writers whose flush can fail
- Handle peer-exit (SIGCHLD / connection reset) before issuing more writes
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
- Daemon::spawn: timed out waiting for child to connect
- Daemon::spawn: timed out reading token from child
- Daemon::from_channel: bincode deserialize
- Daemon::set_context: bincode serialize context
- PidLog::log: failed to flush
AI-assisted analysis of facebook/flow@f88ac94bcf (2026-08-20).
Data as JSON: /api/errors/8746e006d8874c4d.
Report an issue: GitHub.