a-b-street/abstreet · critical
Server error
Error message
Server error: {} What it means
headless's main binds a hyper TCP server on the configured socket address and awaits serve_future. If the server future terminates with a hyper::Error (typically a bind failure like the port already being in use, or a fatal accept/IO error), main panics with "Server error: {err}". This is a fatal startup/runtime abort for the headless map server.
Solutions
- Choose a different --port (or free the port: find and kill the process bound to it, e.g. `lsof -i :PORT`).
- Bind to 0.0.0.0 instead of a specific unavailable IP if running in a container.
- Use an unprivileged port (>1024) or run with appropriate permissions.
- Wrap the serve future with graceful error handling/log instead of panicking if the failure should be survivable.
Example fix
// before
if let Err(err) = serve_future.await {
panic!("Server error: {}", err);
}
// after
if let Err(err) = serve_future.await {
eprintln!("server shut down: {}", err);
std::process::exit(1);
} Defensive patterns
Strategy: validation
Validate before calling
use std::net::TcpListener;
// before starting the server, verify the port is free:
if TcpListener::bind(addr).is_err() {
eprintln!("port {} already in use; pick another", addr.port());
std::process::exit(1);
} Try / catch
// Panics uncatchably; wrap startup in your own bind probe or use // TcpListener::bind + Server::from_tcp to get a Result.
Prevention
- Use unprivileged, configurable ports (>1024) and document them.
- Add readiness/liveness checks that fail fast on port conflicts.
- In containers, align published ports with the bound address.
When it happens
Trigger: Running the headless binary when args.port is already bound by another process, the address isn't available on the host, the port is privileged (<1024) without permissions, or hyper hits a fatal IO error while serving.
Common situations: Starting two instances of the headless server on the same port; a stale/zombie process holding the port; Docker/K8s port mapping conflicts; running as non-root on a privileged port.
Related errors
- Failed to download stuff
- This build of A/B Street stores player data in…
- Can't find the data/ directory
- CityName::new( , ) has a country code that isn't two letters
- Couldn't read_json( )
AI-assisted analysis of a-b-street/abstreet@0964f29315 (2026-09-13).
Data as JSON: /api/errors/ed5a957483c3f05c.
Report an issue: GitHub.
Appendix: source
Thrown at headless/src/main.rs:104
let mut load = LOAD.write().unwrap();
load.rng_seed = args.rng_seed;
load.opts = args.opts;
if let Some(path) = args.scenario {
load.scenario = path;
}
let (map, sim) = load.setup(&mut Timer::new("setup headless"));
*MAP.write().unwrap() = map;
*SIM.write().unwrap() = sim;
}
let addr = std::net::SocketAddr::from((args.ip, args.port));
info!("Listening on http://{}", addr);
let serve_future = Server::bind(&addr).serve(hyper::service::make_service_fn(|_| async {
Ok::<_, hyper::Error>(hyper::service::service_fn(serve_req))
}));
if let Err(err) = serve_future.await {
panic!("Server error: {}", err);
}
}
async fn serve_req(req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
let path = req.uri().path().to_string();
// Url::parse needs an absolute URL
let params: HashMap<String, String> =
url::Url::parse(&format!("http://localhost{}", req.uri()))
.unwrap()
.query_pairs()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect();
let body = hyper::body::to_bytes(req).await?.to_vec();
info!("Handling {}", path);
Ok(
match handle_command(
&path,
¶ms,View on GitHub (pinned to 0964f29315)