fix: per-check client with connect_timeout to guarantee OS-level TCP timeout
This commit is contained in:
parent
7905a8003b
commit
b8b0a9d5e2
|
|
@ -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 method = monitor.method.as_deref().unwrap_or("GET").to_uppercase();
|
||||||
let timeout = std::time::Duration::from_millis(monitor.timeout_ms.unwrap_or(30000));
|
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())
|
let req_method = reqwest::Method::from_bytes(method.as_bytes())
|
||||||
.unwrap_or(reqwest::Method::GET);
|
.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 {
|
if let Some(headers) = &monitor.request_headers {
|
||||||
for (k, v) in 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 is_https = monitor.url.starts_with("https://");
|
||||||
let url_for_cert = monitor.url.clone();
|
let url_for_cert = monitor.url.clone();
|
||||||
|
|
||||||
let timed = tokio::time::timeout(timeout, async {
|
let result: Result<_, String> = async {
|
||||||
let resp = req.send().await?;
|
let resp = req.send().await.map_err(|e| e.to_string())?;
|
||||||
let status = resp.status();
|
let status = resp.status();
|
||||||
let headers: HashMap<String, String> = resp.headers().iter()
|
let headers: HashMap<String, String> = resp.headers().iter()
|
||||||
.filter_map(|(k, v)| Some((k.to_string(), v.to_str().ok()?.to_string())))
|
.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 {
|
if content_len > MAX_BODY_BYTES {
|
||||||
format!("[body truncated: Content-Length {} exceeds 10MB limit]", content_len)
|
format!("[body truncated: Content-Length {} exceeds 10MB limit]", content_len)
|
||||||
} else {
|
} 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)];
|
let truncated = &bytes[..bytes.len().min(MAX_BODY_BYTES)];
|
||||||
String::from_utf8_lossy(truncated).into_owned()
|
String::from_utf8_lossy(truncated).into_owned()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok::<_, reqwest::Error>((status, headers, body))
|
Ok((status, headers, body))
|
||||||
}).await;
|
}.await;
|
||||||
|
|
||||||
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
|
|
||||||
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 {
|
match result {
|
||||||
Err(e) => PingResult {
|
Err(e) => PingResult {
|
||||||
monitor_id: monitor.id.clone(),
|
monitor_id: monitor.id.clone(),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue