facebook/flow · error

clone of socket stream for read failed

Error message

clone of socket stream for read failed

What it means

When the monitor's socket acceptor receives a client, create_ephemeral_connection clones the client SocketStream so one handle reads while the original writes: client_stream.try_clone().expect("clone of socket stream for read failed") (rust_port/crates/flow_server_monitor/src/socket_acceptor.rs:118-121). try_clone duplicates the fd; on failure (EMFILE at the fd ceiling, or the peer already reset the connection) the acceptor thread panics, dropping service for all monitor clients.

Source

Thrown at rust_port/crates/flow_server_monitor/src/socket_acceptor.rs:121

        conn: &Self::Connection,
    ) -> bool {
        conn.write(
            flow_server_env::lsp_prot::MessageFromServer::NotificationFromServer(
                flow_server_env::lsp_prot::NotificationFromServer::PleaseHold(status.0, status.1),
            ),
        )
    }
}

fn create_ephemeral_connection(
    client_stream: SocketStream,
    close: Arc<dyn Fn() + Send + Sync>,
) -> Arc<crate::flow_server_monitor_connection::EphemeralConnection> {
    flow_hh_logger::debug!("Creating a new ephemeral connection");

    let read_stream = client_stream
        .try_clone()
        .expect("clone of socket stream for read failed");
    let write_stream = client_stream;

    let close_for_create = close.clone();
    let (start, conn) = crate::flow_server_monitor_connection::EphemeralConnection::create(
        "some ephemeral connection".to_string(),
        read_stream,
        write_stream,
        move || close_for_create(),
        |msg, connection| {
            handle_ephemeral_request(msg, connection.clone());
        },
    );

    // On exit, do our best to send all pending messages to the waiting client.
    let conn_for_close_on_exit = conn.clone();
    let close_on_exit = async move {
        crate::exit_signal::SIGNAL.notified().await;
        tokio::task::spawn_blocking(move || {

View on GitHub (pinned to f88ac94bcf)

Solutions

  1. Raise ulimit -n for the monitor process (LimitNOFILE for systemd, --ulimit nofile= for Docker)
  2. Confirm exhaustion live: ls /proc/<monitor-pid>/fd | wc -l while clients connect
  3. Reduce the number of concurrent monitor clients
  4. Upstream: treat clone failure as a per-connection error (log and drop that client) instead of a panic in the acceptor

Example fix

// before
let read_stream = client_stream
    .try_clone()
    .expect("clone of socket stream for read failed");

// after: a per-client failure no longer kills the acceptor
let Some(read_stream) = client_stream.try_clone().ok() else {
    eprintln!("Error cloning client stream; dropping this connection");
    close();
    return None;
};
Defensive patterns

Strategy: validation

Validate before calling

// Acceptor-level fd guard before cloning the client stream
let open = std::fs::read_dir("/proc/self/fd").map(|d| d.count()).unwrap_or(0);
if open + 4 >= fd_limit() {
    eprintln!("Refusing monitor client: fd budget nearly exhausted ({open})");
    drop(client_stream);
    return None;
}

Prevention

When it happens

Trigger: A new monitor client connecting when the monitor is at RLIMIT_NOFILE — each ephemeral connection consumes extra fds (the stream plus its clone) — or a client that connects and immediately RSTs so the clone errors.

Common situations: Many editors or CLI tools connecting through the monitor socket concurrently; fd leaks accumulating over long monitor uptime; containers with the default 1024 fd limit.

Related errors


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