fix: pre-flight TCP connect check with hard tokio timeout before reqwest attempt
This commit is contained in:
parent
b8b0a9d5e2
commit
5b0bce65c6
|
|
@ -70,20 +70,60 @@ 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.
|
// Pre-flight TCP connect check with a hard OS-level timeout.
|
||||||
// This ensures the OS-level TCP connect is bounded, since tokio future
|
// This catches hosts where the SYN packet hangs indefinitely —
|
||||||
// cancellation alone cannot interrupt a kernel-level SYN wait.
|
// reqwest/hyper with rustls cannot be cancelled via tokio future drop alone.
|
||||||
let check_client = reqwest::Client::builder()
|
let url_parsed = reqwest::Url::parse(&monitor.url).ok();
|
||||||
.user_agent("PingQL-Monitor/0.1")
|
if let Some(ref u) = url_parsed {
|
||||||
.connect_timeout(timeout)
|
let host = u.host_str().unwrap_or("");
|
||||||
.timeout(timeout)
|
let port = u.port_or_known_default().unwrap_or(443);
|
||||||
.build()
|
let addr = format!("{host}:{port}");
|
||||||
.unwrap_or_else(|_| client.clone());
|
// Resolve DNS first
|
||||||
|
let addrs: Vec<_> = match tokio::net::lookup_host(&addr).await {
|
||||||
|
Ok(a) => a.collect(),
|
||||||
|
Err(e) => {
|
||||||
|
return PingResult {
|
||||||
|
monitor_id: monitor.id.clone(),
|
||||||
|
scheduled_at,
|
||||||
|
jitter_ms,
|
||||||
|
status_code: None,
|
||||||
|
latency_ms: Some(start.elapsed().as_millis() as u64),
|
||||||
|
up: false,
|
||||||
|
error: Some(format!("DNS error: {e}")),
|
||||||
|
cert_expiry_days: None,
|
||||||
|
meta: None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Try TCP connect with hard timeout
|
||||||
|
let tcp_result = tokio::time::timeout(
|
||||||
|
timeout,
|
||||||
|
tokio::net::TcpStream::connect(addrs.as_slice()),
|
||||||
|
).await;
|
||||||
|
if let Err(_) | Ok(Err(_)) = tcp_result {
|
||||||
|
let err = match tcp_result {
|
||||||
|
Err(_) => format!("timed out after {}ms", timeout.as_millis()),
|
||||||
|
Ok(Err(e)) => e.to_string(),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
return PingResult {
|
||||||
|
monitor_id: monitor.id.clone(),
|
||||||
|
scheduled_at,
|
||||||
|
jitter_ms,
|
||||||
|
status_code: None,
|
||||||
|
latency_ms: Some(start.elapsed().as_millis() as u64),
|
||||||
|
up: false,
|
||||||
|
error: Some(err),
|
||||||
|
cert_expiry_days: None,
|
||||||
|
meta: None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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 = check_client.request(req_method, &monitor.url);
|
let mut req = client.request(req_method, &monitor.url).timeout(timeout);
|
||||||
|
|
||||||
if let Some(headers) = &monitor.request_headers {
|
if let Some(headers) = &monitor.request_headers {
|
||||||
for (k, v) in headers {
|
for (k, v) in headers {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue