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
- Always call set_nonblocking(true) on the std listener immediately before from_std.
- Ensure the conversion happens inside a tokio runtime built with .enable_all() (as start_server does).
- Return the io::Error instead of expect-ing so failures log the OS reason (EMFILE, EBADF).
- Raise the fd limit if EMFILE occurs under load.
- 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
- Build the runtime with .enable_all() so the I/O driver exists before from_std.
- Keep from_std inside rt.block_on (or Handle::current context) — never on a bare thread.
- Raise RLIMIT_NOFILE in containerized deployments to avoid EMFILE at registration.
- Set non-blocking mode first; from_std on a blocking socket is the #1 cause.
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
- Failed to set listener to non-blocking
- Failed to create tokio runtime for embedded server
- Failed to listen for SIGINT
- Failed to listen for SIGTERM
- Failed to create Tokio runtime
AI-assisted analysis of RightNow-AI/openfang@acf2587e46 (2026-09-02).
Data as JSON: /api/errors/c0d931705448afb9.
Report an issue: GitHub.