block/buzz · error · anyhow::Error
TCP server error
Error message
TCP server error: {e} What it means
In the UDS+TCP mode of serve(), axum::serve drives the main TCP listener after a successful bind; any error from the accept/serve loop surfaces here as 'TCP server error'. Unlike the bind errors, this happens at runtime — the listener was healthy and then the server future failed. In practice axum/hyper only errors out of serve() on unrecoverable accept errors (e.g. EMFILE fd exhaustion) or a poisoned connection state; ordinary per-connection errors are handled internally.
Solutions
- Check fd limits and raise them: `ulimit -n 65536` (systemd: LimitNOFILE=65536) and look for fd leaks: `ls /proc/<pid>/fd | wc -l` over time.
- Capture the underlying io::Error kind from the anyhow chain (e.g. RUST_LOG=debug) — EMFILE points to fd exhaustion, EBADF to runtime misuse.
- Restart under a supervisor (systemd/K8s restart policy) — serve() returning Err exits main, and the stale-socket cleanup handles UDS reuse on restart.
- If it recurs, profile connection churn (`ss -tnp state established '( dport = :<port> )' | wc -l`) and tune connection timeouts/idle draining.
Defensive patterns
Strategy: try-catch
Validate before calling
# raise fd limits before launch so accept() never starves ulimit -n 65536
Try / catch
// treat serve() failure as terminal but observable, and let the supervisor restart
if let Err(e) = serve(router, health_router, state).await {
tracing::error!(error = %e, "relay serve failed, exiting");
std::process::exit(1);
} Prevention
- Set LimitNOFILE/ulimit generously (64k+) on relay deployments.
- Monitor open-fd counts (`/proc/<pid>/fd`) as a leading indicator of accept-loop EMFILE.
- Run under a restart policy — this error path exits main by design.
- Correlate occurrences with connection spikes in metrics before assuming a code bug.
When it happens
Trigger: axum::serve(tcp_listener, router.into_make_service_with_connect_info::<SocketAddr>()) .with_graceful_shutdown(...) returning Err at crates/buzz-relay/src/main.rs:1364-1372: accept() failing persistently with EMFILE/ENFILE (process or system fd limit exhausted under connection churn), ENOMEM, or EBADF after external fd manipulation. It also propagates if the tokio runtime the serve future runs on is dropped unexpectedly.
Common situations: Relay under heavy WebSocket connection load hitting the `ulimit -n` cap (EMFILE on accept); a leak of sockets elsewhere in the process starving the accept loop; running under a supervisor that kills and the runtime shuts down mid-serve; extremely rare hyper internal errors on malformed connections.
Related errors
AI-assisted analysis of block/buzz@ef2aa1ae38 (2026-08-20).
Data as JSON: /api/errors/f29439ac92c7ce42.
Report an issue: GitHub.
Appendix: source
Thrown at crates/buzz-relay/src/main.rs:1489
let uds_handle = tokio::spawn(async move {
axum::serve(uds_listener, router_uds.into_make_service())
.with_graceful_shutdown(async move {
uds_rx.changed().await.ok();
})
.await
.ok();
});
let mut tcp_rx = shutdown_tx.subscribe();
axum::serve(
tcp_listener,
router.into_make_service_with_connect_info::<std::net::SocketAddr>(),
)
.with_graceful_shutdown(async move {
tcp_rx.changed().await.ok();
})
.await
.map_err(|e| anyhow::anyhow!("TCP server error: {e}"))?;
let hard_shutdown = shutdown_handle
.await
.map_err(|e| anyhow::anyhow!("Shutdown task failed: {e}"))?;
uds_handle.abort();
hard_shutdown.abort();
return Ok(());
}
#[cfg(not(unix))]
if config.uds_path.is_some() {
tracing::warn!("BUZZ_UDS_PATH set but UDS not supported on this platform");
}
// TCP-only path.
let mut tcp_rx = shutdown_tx.subscribe();
axum::serve(
tcp_listener,View on GitHub (pinned to ef2aa1ae38)