fix(web_ui): cap bind_with_retry at 12 attempts, then return AddrInUse
This commit is contained in:
parent
bdfeac80a7
commit
7c851c5dd3
1 changed files with 17 additions and 15 deletions
|
|
@ -192,12 +192,16 @@ fn bind_unix(path: &Path) -> Result<tokio::net::UnixListener> {
|
||||||
Ok(listener)
|
Ok(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Bind a TCP listener with `SO_REUSEADDR` set, retrying on
|
/// Maximum bind attempts before `bind_with_retry` gives up on `AddrInUse`.
|
||||||
/// `AddrInUse` indefinitely with exponential backoff capped at 2s.
|
const MAX_BIND_ATTEMPTS: u32 = 12;
|
||||||
/// First 12 attempts log at WARN; subsequent attempts log at INFO so
|
|
||||||
/// a long-held stale socket doesn't flood the journal.
|
/// Bind a TCP listener with `SO_REUSEADDR` set, retrying on `AddrInUse` with
|
||||||
|
/// exponential backoff capped at 2s, up to [`MAX_BIND_ATTEMPTS`] attempts. If
|
||||||
|
/// the port is still held after the final attempt, returns the `AddrInUse`
|
||||||
|
/// error rather than looping forever (a genuine collision needs the operator,
|
||||||
|
/// not an unbounded wait).
|
||||||
///
|
///
|
||||||
/// Uncapped retry + dashboard-banner-on-real-collision rationale:
|
/// Retry rationale + dashboard-banner-on-real-collision:
|
||||||
/// see [`docs/web-ui/shape.md::Listener bind`](../../../docs/web-ui/shape.md).
|
/// see [`docs/web-ui/shape.md::Listener bind`](../../../docs/web-ui/shape.md).
|
||||||
async fn bind_with_retry(addr: SocketAddr, label: &str) -> Result<tokio::net::TcpListener> {
|
async fn bind_with_retry(addr: SocketAddr, label: &str) -> Result<tokio::net::TcpListener> {
|
||||||
let mut delay_ms = 250u64;
|
let mut delay_ms = 250u64;
|
||||||
|
|
@ -215,17 +219,15 @@ async fn bind_with_retry(addr: SocketAddr, label: &str) -> Result<tokio::net::Tc
|
||||||
}
|
}
|
||||||
Err(e) if e.kind() == std::io::ErrorKind::AddrInUse => {
|
Err(e) if e.kind() == std::io::ErrorKind::AddrInUse => {
|
||||||
let attempt = attempts + 1;
|
let attempt = attempts + 1;
|
||||||
if attempt <= 12 {
|
if attempt >= MAX_BIND_ATTEMPTS {
|
||||||
tracing::warn!(
|
return Err(e).with_context(|| {
|
||||||
%addr, attempt,
|
format!("bind {label} on {addr}: still AddrInUse after {attempt} attempts")
|
||||||
"{label}: AddrInUse, retrying in {delay_ms}ms"
|
});
|
||||||
);
|
|
||||||
} else {
|
|
||||||
tracing::info!(
|
|
||||||
%addr, attempt,
|
|
||||||
"{label}: AddrInUse still holding, retrying in {delay_ms}ms"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
tracing::warn!(
|
||||||
|
%addr, attempt,
|
||||||
|
"{label}: AddrInUse, retrying in {delay_ms}ms"
|
||||||
|
);
|
||||||
tokio::time::sleep(std::time::Duration::from_millis(delay_ms)).await;
|
tokio::time::sleep(std::time::Duration::from_millis(delay_ms)).await;
|
||||||
attempts += 1;
|
attempts += 1;
|
||||||
delay_ms = (delay_ms * 2).min(2000);
|
delay_ms = (delay_ms * 2).min(2000);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue