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

  1. Choose a different --port (or free the port: find and kill the process bound to it, e.g. `lsof -i :PORT`).
  2. Bind to 0.0.0.0 instead of a specific unavailable IP if running in a container.
  3. Use an unprivileged port (>1024) or run with appropriate permissions.
  4. 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

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


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,
            &params,

View on GitHub (pinned to 0964f29315)