fix: spawn cert check as independent task, never blocks main request timeout

This commit is contained in:
M1 2026-03-18 12:35:58 +04:00
parent dbbc9c00cc
commit 68093131fa
1 changed files with 42 additions and 31 deletions

View File

@ -93,18 +93,20 @@ async fn run_check(client: &reqwest::Client, monitor: &Monitor, scheduled_at: Op
let is_https = monitor.url.starts_with("https://"); let is_https = monitor.url.starts_with("https://");
let url_clone = monitor.url.clone(); let url_clone = monitor.url.clone();
// Run the HTTP request and cert check concurrently, both under the same timeout. // Wrap request + body read in a hard timeout.
// This prevents a hanging TCP connect in the cert check from blocking the whole check. // Cert check runs as a background task with a shorter cap so it never blocks
let timed = tokio::time::timeout(timeout, async { // the main check — if the cert TLS connect hangs (e.g. site totally down),
let cert_future = async { // we still report the result from the HTTP side within the configured timeout.
if is_https { let cert_handle = if is_https {
check_cert_expiry(&url_clone).await.ok().flatten() Some(tokio::spawn(tokio::time::timeout(
std::time::Duration::from_secs(10),
async move { check_cert_expiry(&url_clone).await },
)))
} else { } else {
None None
}
}; };
let req_future = async { let timed = tokio::time::timeout(timeout, async {
let resp = req.send().await?; let resp = req.send().await?;
let status = resp.status(); let status = resp.status();
let headers: HashMap<String, String> = resp.headers().iter() let headers: HashMap<String, String> = resp.headers().iter()
@ -123,12 +125,21 @@ async fn run_check(client: &reqwest::Client, monitor: &Monitor, scheduled_at: Op
} }
}; };
Ok::<_, reqwest::Error>((status, headers, body)) Ok::<_, reqwest::Error>((status, headers, body))
};
let (cert_result, req_result) = tokio::join!(cert_future, req_future);
req_result.map(|(status, headers, body)| (status, headers, body, cert_result))
}).await; }).await;
// Collect cert result — give it up to 2s after the main request finishes,
// then abort. This way a fast site still gets cert info, but a hung cert
// check never blocks the ping result.
let cert_expiry_days = match cert_handle {
Some(handle) => {
match tokio::time::timeout(std::time::Duration::from_secs(2), handle).await {
Ok(Ok(Ok(Ok(days)))) => days,
_ => None,
}
},
None => None,
};
let latency_ms = start.elapsed().as_millis() as u64; let latency_ms = start.elapsed().as_millis() as u64;
// Flatten timeout + reqwest errors into a single result // Flatten timeout + reqwest errors into a single result
@ -150,7 +161,7 @@ async fn run_check(client: &reqwest::Client, monitor: &Monitor, scheduled_at: Op
cert_expiry_days: None, cert_expiry_days: None,
meta: None, meta: None,
}, },
Ok((status_raw, headers, body, cert_expiry_days)) => { Ok((status_raw, headers, body)) => {
let status = status_raw.as_u16(); let status = status_raw.as_u16();
// Evaluate query if present // Evaluate query if present