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

  1. 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.
  2. Capture the underlying io::Error kind from the anyhow chain (e.g. RUST_LOG=debug) — EMFILE points to fd exhaustion, EBADF to runtime misuse.
  3. Restart under a supervisor (systemd/K8s restart policy) — serve() returning Err exits main, and the stale-socket cleanup handles UDS reuse on restart.
  4. 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

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)