RightNow-AI/openfang · error

Failed to set listener to non-blocking

Error message

Failed to set listener to non-blocking

What it means

This panic comes from `std_listener.set_nonblocking(true).expect(...)` in run_embedded_server (crates/openfang-desktop/src/server.rs:126). Before wrapping the bound std::net::TcpListener with tokio via TcpListener::from_std, the socket must be switched to non-blocking mode, because from_std panics/errors on a blocking socket. set_nonblocking returns io::Result and fails only if the underlying syscall (fcntl ioctl FIONBIO) fails on the socket fd.

Source

Thrown at crates/openfang-desktop/src/server.rs:126

        server_thread: Some(server_thread),
        shutdown_initiated,
    })
}

/// Run the axum server inside a tokio runtime, shut down when the watch
/// channel fires.
async fn run_embedded_server(
    kernel: Arc<OpenFangKernel>,
    std_listener: TcpListener,
    listen_addr: SocketAddr,
    mut shutdown_rx: watch::Receiver<bool>,
) {
    let (app, state) = build_router(kernel, listen_addr).await;

    // Convert std TcpListener → tokio TcpListener
    std_listener
        .set_nonblocking(true)
        .expect("Failed to set listener to non-blocking");
    let listener = tokio::net::TcpListener::from_std(std_listener)
        .expect("Failed to convert std TcpListener to tokio");

    info!("OpenFang embedded server listening on http://{listen_addr}");

    let server = axum::serve(
        listener,
        app.into_make_service_with_connect_info::<SocketAddr>(),
    )
    .with_graceful_shutdown(async move {
        let _ = shutdown_rx.wait_for(|v| *v).await;
        info!("Embedded server received shutdown signal");
    });

    if let Err(e) = server.await {
        error!("Embedded server error: {e}");
    }

View on GitHub (pinned to acf2587e46)

Solutions

  1. Log the underlying io::Error instead of panicking: map the expect to a Result and return it from run_embedded_server so the caller can shut down cleanly.
  2. Verify the listener is still open (no concurrent close) between bind and this call.
  3. Check the process fd limit (ulimit -n) if errors correlate with heavy load.
  4. If conversion keeps failing, construct the tokio listener differently or retry the whole listener setup with a fresh bind.

Example fix

// before
std_listener
    .set_nonblocking(true)
    .expect("Failed to set listener to non-blocking");
// after
std_listener
    .set_nonblocking(true)
    .map_err(|e| ServerError::ListenerSetup(format!("set_nonblocking: {e}")))?;
Defensive patterns

Strategy: validation

Validate before calling

// Before conversion, confirm the socket fd is still valid
let fd = std_listener.as_raw_fd();
if fd < 0 {
    return Err("listener fd already closed");
}

Try / catch

match std_listener.set_nonblocking(true) {
    Ok(()) => { /* proceed to from_std */ }
    Err(e) => log::error!("set_nonblocking failed: {e}"), // abort server startup cleanly
}

Prevention

When it happens

Trigger: Calling set_nonblocking on an std TcpListener whose file descriptor is invalid, already closed, or on which the OS refuses the FIONBIO ioctl (fd exhaustion, EBADF from a raced shutdown).

Common situations: Extremely rare in practice; typically seen when the listener fd was closed by another thread before this call, under fd-limit exhaustion, or on exotic platforms/embedded targets where the ioctl is unsupported.

Related errors


AI-assisted analysis of RightNow-AI/openfang@acf2587e46 (2026-09-02). Data as JSON: /api/errors/8ee828c95f388090. Report an issue: GitHub.