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
- 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.
- Verify the listener is still open (no concurrent close) between bind and this call.
- Check the process fd limit (ulimit -n) if errors correlate with heavy load.
- 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
- Always call set_nonblocking(true) directly before TcpListener::from_std — never skip it.
- Do not close or move the std listener from other threads between bind and conversion.
- Keep bind → set_nonblocking → from_std in one function so ordering is obvious.
- Monitor fd usage (open files) if the server binds many listeners.
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
- Failed to convert std TcpListener to tokio
- Failed to create tokio runtime for embedded server
- MCP message too large: {content_length} bytes (max {MAX_MCP_
- Failed to listen for SIGINT
- Failed to listen for SIGTERM
AI-assisted analysis of RightNow-AI/openfang@acf2587e46 (2026-09-02).
Data as JSON: /api/errors/8ee828c95f388090.
Report an issue: GitHub.