fix: run cert check concurrently with request so a hanging TCP connect can't block the timeout

This commit is contained in:
M1 2026-03-18 12:33:22 +04:00
parent b41faff2ad
commit dbbc9c00cc
1 changed files with 35 additions and 29 deletions

View File

@ -70,16 +70,6 @@ async fn run_check(client: &reqwest::Client, monitor: &Monitor, scheduled_at: Op
let method = monitor.method.as_deref().unwrap_or("GET").to_uppercase();
let timeout = std::time::Duration::from_millis(monitor.timeout_ms.unwrap_or(30000));
// Check cert expiry for HTTPS URLs — bounded by the same timeout
let cert_expiry_days = if monitor.url.starts_with("https://") {
match tokio::time::timeout(timeout, check_cert_expiry(&monitor.url)).await {
Ok(Ok(days)) => days,
_ => None,
}
} else {
None
};
let req_method = reqwest::Method::from_bytes(method.as_bytes())
.unwrap_or(reqwest::Method::GET);
@ -100,27 +90,43 @@ async fn run_check(client: &reqwest::Client, monitor: &Monitor, scheduled_at: Op
req = req.body(body.clone());
}
// Wrap the entire request + body read in a single timeout so slow response
// bodies don't slip through after headers arrive within the window.
let timed = tokio::time::timeout(timeout, async {
let resp = req.send().await?;
let status = resp.status();
let headers: HashMap<String, String> = resp.headers().iter()
.filter_map(|(k, v)| Some((k.to_string(), v.to_str().ok()?.to_string())))
.collect();
let is_https = monitor.url.starts_with("https://");
let url_clone = monitor.url.clone();
const MAX_BODY_BYTES: usize = 10 * 1024 * 1024;
let body = {
let content_len = resp.content_length().unwrap_or(0) as usize;
if content_len > MAX_BODY_BYTES {
format!("[body truncated: Content-Length {} exceeds 10MB limit]", content_len)
// Run the HTTP request and cert check concurrently, both under the same timeout.
// This prevents a hanging TCP connect in the cert check from blocking the whole check.
let timed = tokio::time::timeout(timeout, async {
let cert_future = async {
if is_https {
check_cert_expiry(&url_clone).await.ok().flatten()
} else {
let bytes = resp.bytes().await?;
let truncated = &bytes[..bytes.len().min(MAX_BODY_BYTES)];
String::from_utf8_lossy(truncated).into_owned()
None
}
};
Ok::<_, reqwest::Error>((status, headers, body))
let req_future = async {
let resp = req.send().await?;
let status = resp.status();
let headers: HashMap<String, String> = resp.headers().iter()
.filter_map(|(k, v)| Some((k.to_string(), v.to_str().ok()?.to_string())))
.collect();
const MAX_BODY_BYTES: usize = 10 * 1024 * 1024;
let body = {
let content_len = resp.content_length().unwrap_or(0) as usize;
if content_len > MAX_BODY_BYTES {
format!("[body truncated: Content-Length {} exceeds 10MB limit]", content_len)
} else {
let bytes = resp.bytes().await?;
let truncated = &bytes[..bytes.len().min(MAX_BODY_BYTES)];
String::from_utf8_lossy(truncated).into_owned()
}
};
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;
let latency_ms = start.elapsed().as_millis() as u64;
@ -141,10 +147,10 @@ async fn run_check(client: &reqwest::Client, monitor: &Monitor, scheduled_at: Op
latency_ms: Some(latency_ms),
up: false,
error: Some(e),
cert_expiry_days,
cert_expiry_days: None,
meta: None,
},
Ok((status_raw, headers, body)) => {
Ok((status_raw, headers, body, cert_expiry_days)) => {
let status = status_raw.as_u16();
// Evaluate query if present