diff --git a/apps/monitor/src/runner.rs b/apps/monitor/src/runner.rs index 880b5a3..4aacfa9 100644 --- a/apps/monitor/src/runner.rs +++ b/apps/monitor/src/runner.rs @@ -70,10 +70,20 @@ 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)); + // Build a per-check client with connect_timeout = monitor timeout. + // This ensures the OS-level TCP connect is bounded, since tokio future + // cancellation alone cannot interrupt a kernel-level SYN wait. + let check_client = reqwest::Client::builder() + .user_agent("PingQL-Monitor/0.1") + .connect_timeout(timeout) + .timeout(timeout) + .build() + .unwrap_or_else(|_| client.clone()); + let req_method = reqwest::Method::from_bytes(method.as_bytes()) .unwrap_or(reqwest::Method::GET); - let mut req = client.request(req_method, &monitor.url).timeout(timeout); + let mut req = check_client.request(req_method, &monitor.url); if let Some(headers) = &monitor.request_headers { for (k, v) in headers { @@ -93,8 +103,8 @@ async fn run_check(client: &reqwest::Client, monitor: &Monitor, scheduled_at: Op let is_https = monitor.url.starts_with("https://"); let url_for_cert = monitor.url.clone(); - let timed = tokio::time::timeout(timeout, async { - let resp = req.send().await?; + let result: Result<_, String> = async { + let resp = req.send().await.map_err(|e| e.to_string())?; let status = resp.status(); let headers: HashMap = resp.headers().iter() .filter_map(|(k, v)| Some((k.to_string(), v.to_str().ok()?.to_string()))) @@ -106,23 +116,16 @@ async fn run_check(client: &reqwest::Client, monitor: &Monitor, scheduled_at: Op if content_len > MAX_BODY_BYTES { format!("[body truncated: Content-Length {} exceeds 10MB limit]", content_len) } else { - let bytes = resp.bytes().await?; + let bytes = resp.bytes().await.map_err(|e| e.to_string())?; let truncated = &bytes[..bytes.len().min(MAX_BODY_BYTES)]; String::from_utf8_lossy(truncated).into_owned() } }; - Ok::<_, reqwest::Error>((status, headers, body)) - }).await; + Ok((status, headers, body)) + }.await; let latency_ms = start.elapsed().as_millis() as u64; - // Flatten timeout + reqwest errors into a single result - let result = match timed { - Err(_) => Err(format!("timed out after {}ms", timeout.as_millis())), - Ok(Err(e)) => Err(e.to_string()), - Ok(Ok(v)) => Ok(v), - }; - match result { Err(e) => PingResult { monitor_id: monitor.id.clone(),