stamparm/maltrail · error
connect
Error message
connect
What it means
The test connects a TcpStream to the listener returned by spawn() and .expect("connect") panics if the connection cannot be established. Since the address is a just-bound loopback listener, failure indicates the server thread died, the listener was closed, or the connection was refused/timed out in the environment.
Solutions
- Check whether the spawned server thread panicked (capture its JoinHandle/panic output) before connecting
- Add a readiness wait (poll until the port accepts) instead of connecting immediately after spawn
- Verify loopback connections are permitted in the CI/sandbox environment
- Surface the io::Error in the panic message for diagnosis
Example fix
// before
let mut stream = std::net::TcpStream::connect(bound).expect("connect");
// after
let mut stream = std::net::TcpStream::connect(bound)
.unwrap_or_else(|e| panic!("connect to metrics server {bound} failed: {e}")); Defensive patterns
Strategy: retry
Validate before calling
std::net::TcpStream::connect_timeout(&bound, std::time::Duration::from_secs(2)).map(|s| drop(s)).map_err(|e| format!("metrics server not accepting: {e}"))?; Try / catch
let mut stream = std::net::TcpStream::connect(bound).unwrap_or_else(|e| panic!("connect to {bound}: {e}")); Prevention
- Wait for the spawned server to be ready before connecting
- Assert the server thread is alive (JoinHandle::is_finished) before scraping
- Allow loopback sockets in sandboxed CI
When it happens
Trigger: TcpStream::connect(bound) in a_scrape_returns_the_metrics_over_http returns Err because the spawned metrics server thread exited or never accepted, the listener was dropped, or loopback connections are blocked.
Common situations: Server thread panicking before accept; spawn() returning an address whose listener was immediately dropped; sandboxed CI blocking loopback connect; firewall or seccomp policy refusing local sockets.
Understand the failure class
Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.
Related errors
AI-assisted analysis of stamparm/maltrail@77cfb06d76 (2026-09-13).
Data as JSON: /api/errors/0319294ea7cedca0.
Report an issue: GitHub.
Appendix: source
Thrown at sensor/src/stats.rs:337
] {
assert!(text.contains(&format!("# HELP {name} ")), "missing HELP for {name}:\n{text}");
assert!(text.contains(&format!("# TYPE {name} ")), "missing TYPE for {name}");
}
assert!(text.contains("maltrail_trails 1505265"), "{text}");
assert!(text.contains("maltrail_workers 2"), "{text}");
assert!(text.contains("maltrail_worker_packets_total{worker=\"1\"}"), "{text}");
// Monotonic counters must end in _total so `rate()` works.
for line in text.lines().filter(|l| l.starts_with("# TYPE ") && l.ends_with(" counter")) {
let name = line.split_whitespace().nth(2).unwrap();
assert!(name.ends_with("_total"), "counter {name} must end in _total");
}
}
#[test]
fn a_scrape_returns_the_metrics_over_http() {
let registry = Arc::new(Registry::new(1));
let bound = spawn("127.0.0.1:0", registry, std::time::Instant::now()).expect("bind");
let mut stream = std::net::TcpStream::connect(bound).expect("connect");
stream.write_all(b"GET /metrics HTTP/1.1\r\nHost: localhost\r\n\r\n").unwrap();
let mut response = String::new();
stream.read_to_string(&mut response).unwrap();
assert!(response.starts_with("HTTP/1.1 200 OK"), "{response}");
assert!(response.contains("text/plain; version=0.0.4"), "{response}");
assert!(response.contains("maltrail_up 1"), "{response}");
}
#[test]
fn a_bad_address_is_reported_not_fatal() {
let registry = Arc::new(Registry::new(1));
assert!(spawn("300.300.300.300:1", registry, std::time::Instant::now()).is_err());
}
}
View on GitHub (pinned to 77cfb06d76)