fix(relay): avoid overflow when rounding microsecond timeouts

pg_timeout_millis rounded `us` values with `(value + 500) / 1_000`, which
overflows for the top 500 representable u128 magnitudes — a debug panic during
config load, or in release a wrapped near-zero millisecond value that slips
past the range check and hands the original gigantic string to every pool's
after_connect. Round without the intermediate, and cover u128::MAX and the
first overflowing value in the fallback test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Eli Foster <efoster@squareup.com>
This commit is contained in:
Eli Foster
2026-08-11 10:36:08 +10:00
committed by Alex Rosenzweig
co-authored by Claude Opus 5
parent 332ef5ff56
commit d76acde5fe
+8 -1
View File
@@ -312,7 +312,9 @@ fn parse_bind_addr(raw: &str) -> Result<SocketAddr, ConfigError> {
fn pg_timeout_millis(magnitude: &str, unit: &str) -> Option<u128> {
let value = magnitude.parse::<u128>().ok()?;
match unit {
"us" => Some((value + 500) / 1_000),
// Round half up without the `value + 500` intermediate, which overflows
// for the top 500 representable microsecond values.
"us" => Some(value / 1_000 + u128::from(value % 1_000 >= 500)),
"" | "ms" => Some(value),
"s" => value.checked_mul(1_000),
"min" => value.checked_mul(60_000),
@@ -1378,6 +1380,11 @@ mod tests {
// Wider than any integer type — must fall back, not overflow.
"999999999999999999999999999999999999999999d",
"99999999999999999999999999999999999999999999999999",
// Parses as u128, so unlike the two above it reaches the unit
// conversion — where rounding must not overflow on the way to the
// range check.
&format!("{}us", u128::MAX),
&format!("{}us", u128::MAX - 499),
] {
assert_eq!(
pg_timeout_or_default(Some(rejected), "30s"),