RightNow-AI/openfang · critical

Failed to convert std TcpListener to tokio

Error message

Failed to convert std TcpListener to tokio

What it means

This panic comes from `tokio::net::TcpListener::from_std(std_listener).expect(...)` in run_embedded_server (crates/openfang-desktop/src/server.rs:128). from_std converts an already-bound, already-listening std TcpListener into a tokio one by registering it with the tokio runtime's I/O driver. It fails if the socket is in blocking mode, the fd cannot be registered with epoll/kqueue, or the runtime has no I/O driver enabled.

Source

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

    })
}

/// 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}");
    }

    // Clean up channel bridges
    {

View on GitHub (pinned to acf2587e46)

Solutions

  1. Always call set_nonblocking(true) on the std listener immediately before from_std.
  2. Ensure the conversion happens inside a tokio runtime built with .enable_all() (as start_server does).
  3. Return the io::Error instead of expect-ing so failures log the OS reason (EMFILE, EBADF).
  4. Raise the fd limit if EMFILE occurs under load.
  5. Avoid sharing/closing the raw listener fd concurrently with this conversion.

Example fix

// before
let listener = tokio::net::TcpListener::from_std(std_listener)
    .expect("Failed to convert std TcpListener to tokio");
// after
std_listener.set_nonblocking(true)?;
let listener = tokio::net::TcpListener::from_std(std_listener)
    .map_err(|e| ServerError::ListenerSetup(format!("from_std: {e}")))?;
Defensive patterns

Strategy: try-catch

Validate before calling

// Ensure listener is non-blocking before from_std
std_listener.set_nonblocking(true)?;

Try / catch

let listener = tokio::net::TcpListener::from_std(std_listener)
    .map_err(|e| {
        log::error!("std→tokio listener conversion failed: {e}");
        e
    })?; // return Result from run_embedded_server instead of expect

Prevention

When it happens

Trigger: (1) Skipping or failing the preceding set_nonblocking(true) call; (2) calling from_std outside a runtime with enable_io() (here it runs inside rt.block_on, so the driver exists, but fd registration can still fail); (3) fd exhaustion or the socket being closed before conversion.

Common situations: Adapting this code into a context whose runtime was built without .enable_all()/.enable_io(); refactoring that drops the set_nonblocking step; running in containers with exhausted file descriptors; passing a listener fd that another component closed.

Related errors


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