{"record":{"id":"4c0a91fae5a8e330","repo":"linera-io/linera-protocol","slug":"failed-to-bind-to-address","errorCode":null,"errorMessage":"Failed to bind to address","messagePattern":"Failed to bind to address","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"linera-metrics/src/monitoring_server.rs","lineNumber":104,"sourceCode":"///\n/// `register_metrics` is the caller's `init_metrics`. It is a parameter rather than a direct\n/// call because this crate sits below `linera-views` and friends in the dependency graph and\n/// cannot reach their metrics; taking it here makes forgetting to register a compile error\n/// instead of a metric that silently only appears once its code path first runs.\npub fn start_metrics(\n    address: impl ToSocketAddrs + Debug + Send + 'static,\n    shutdown_signal: CancellationToken,\n    memory_profiling: MemoryProfiling,\n    register_metrics: impl FnOnce(),\n) {\n    crate::runtime_metrics::register();\n    register_metrics();\n    let app = metrics_router(memory_profiling);\n\n    tokio::spawn(async move {\n        let listener = tokio::net::TcpListener::bind(address)\n            .await\n            .expect(\"Failed to bind to address\");\n        let address = listener.local_addr().expect(\"Failed to get local address\");\n\n        info!(\"Starting to serve metrics on {:?}\", address);\n        if let Err(e) = axum::serve(listener, app)\n            .with_graceful_shutdown(shutdown_signal.cancelled_owned())\n            .await\n        {\n            panic!(\"Error serving metrics: {e}\");\n        }\n    });\n}\n\nfn metrics_router(memory_profiling: MemoryProfiling) -> Router {\n    #[cfg(feature = \"jemalloc\")]\n    if memory_profiling == MemoryProfiling::Enabled {\n        match MemoryProfiler::check_prof_ctl() {\n            Ok(()) => {\n                info!(\"Memory profiling enabled, registering /debug/pprof and /debug/flamegraph endpoints\");","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-metrics/src/monitoring_server.rs#L86-L122","documentation":"start_metrics (linera-metrics/src/monitoring_server.rs:91) binds a TcpListener for the /metrics HTTP server inside a tokio::spawn and expects bind to succeed. Failure means the address could not be bound: port already in use, permission denied on privileged ports (<1024) for non-root users, or an invalid/unavailable address. Because the bind happens in a detached spawned task, the panic does not kill the process - the service silently runs on without metrics, which makes this easy to miss.","triggerScenarios":"Starting a linera service (validator, proxy, exporter via start_metrics_with_profiling) when metrics_port is already bound by another process, or configuring a privileged port while running non-root.","commonSituations":"Two linera components on one host configured with the same metrics port; port reused across container restarts with a stale holder; firewall/SELinux denying bind; port < 1024 in a non-root container.","solutions":["Free the conflicting port: find the holder with `ss -ltnp | grep <port>` and stop it.","Configure a different, unprivileged metrics_port for this service.","Use port 0 in test setups to get an ephemeral port automatically.","After startup, curl the /metrics endpoint to verify the server actually came up."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Verify the metrics port is free before starting the service:\nasync fn ensure_metrics_port_free(addr: std::net::SocketAddr) -> anyhow::Result<()> {\n    match tokio::net::TcpListener::bind(addr).await {\n        Ok(l) => { drop(l); Ok(()) }\n        Err(e) => Err(anyhow::anyhow!(\"metrics address {addr} unavailable: {e}\")),\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Give every linera process on a host a unique metrics port; document them per service.","Use port 0 for ephemeral metrics ports in tests and read back the actual port from logs.","Add a post-start check: curl http://localhost:<port>/metrics must return 200; alert if not, since the bind panic is swallowed by the spawned task."],"tags":["linera-metrics","port-in-use","bind-failure","observability","panic"],"backgroundTag":"address-already-in-use","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}