linera-io/linera-protocol · error
a running notification server
Error message
a running notification server
What it means
start_notification_server (linera-exporter/src/exporter_service.rs) serves the gRPC NotifierServiceServer on the configured endpoint with serve_with_shutdown and expects success. The expect fires when tonic fails to serve the endpoint - overwhelmingly a bind failure such as 'address already in use', plus invalid endpoint addresses or socket permission errors. This runs during exporter startup, so the process aborts before serving notifications.
Solutions
- Find and stop the process holding the port: `ss -ltnp | grep <port>` or `lsof -i :<port>`, then kill it.
- Change the notification port in the exporter config to a free one.
- Verify the endpoint address format in the config (e.g. 0.0.0.0:PORT or 127.0.0.1:PORT).
- If two exporters are intended, give each its own notification port.
Defensive patterns
Strategy: validation
Validate before calling
// Before starting the exporter, verify the notification port is free:
let probe = tokio::net::TcpListener::bind(notification_endpoint).await;
match probe {
Ok(l) => drop(l), // port free; brief TOCTOU window is acceptable at startup
Err(e) if e.kind() == std::io::ErrorKind::AddrInUse => {
return Err(anyhow::anyhow!("notification port already in use: {notification_endpoint}"));
}
Err(e) => return Err(e.into()),
} Prevention
- Assign each exporter instance a distinct notification port in its config.
- In containers, let the orchestrator allocate host ports instead of hardcoding.
- After startup, health-check the gRPC endpoint (e.g. grpcurl health check) to confirm it bound.
When it happens
Trigger: Starting linera-exporter when the notification server port (from the config's notification section) is already bound by another process - a second exporter instance, a validator proxy using the same port, or a leftover process. Also a malformed endpoint address in the config.
Common situations: Duplicate exporter instances in a container/k8s setup pointing at the same port; port collision between the exporter's notification server and another service; stale process still holding the port after a crash.
Related errors
- a successful running of the server
- Exporter URI should be valid
- Failed to bind to address
- Failed to clone storage
- Failed to create log file
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/98a4b678bb544e34.
Report an issue: GitHub.
Appendix: source
Thrown at linera-exporter/src/exporter_service.rs:96
cancellation_token: CancellationToken,
) -> core::result::Result<(), ExporterError> {
let endpoint = get_address(port);
info!(
"Starting linera_exporter_service on endpoint = {}",
endpoint
);
let (health_reporter, health_service) = tonic_health::server::health_reporter();
health_reporter
.set_serving::<NotifierServiceServer<Self>>()
.await;
Server::builder()
.add_service(health_service)
.add_service(NotifierServiceServer::new(self))
.serve_with_shutdown(endpoint, cancellation_token.cancelled_owned())
.await
.expect("a running notification server");
Ok(())
}
}
fn parse_notification(notification: Notification) -> core::result::Result<BlockId, ExporterError> {
let chain_id = notification
.chain_id
.ok_or(BadNotificationKind::InvalidChainId { inner: None })?
.try_into()
.map_err(|err| BadNotificationKind::InvalidChainId { inner: Some(err) })?;
let reason = bincode::deserialize::<Reason>(¬ification.reason)
.map_err(|err| BadNotificationKind::InvalidReason { inner: Some(err) })?;
if let Reason::NewBlock { height, hash } = reason {
return Ok(BlockId::new(chain_id, hash, height));
}View on GitHub (pinned to 6c226ddcb3)