feat: refactor stage 1

This commit is contained in:
2026-04-08 08:58:44 +04:00
parent 8e554498f0
commit 1f01a00ad6
9 changed files with 143 additions and 17 deletions
+50 -12
View File
@@ -85,8 +85,12 @@ pub async fn fetch_and_run(
}
}
let timeout_ms = monitor.timeout_ms.unwrap_or(30000);
let deadline = std::time::Duration::from_millis(timeout_ms + 5000);
let result = match tokio::time::timeout(deadline, run_check(&client, &monitor, scheduled_at_iso.clone(), &region_owned, &run_id_owned)).await {
let attempts = monitor.max_retries.saturating_add(1) as u64;
let retry_gap_s = monitor.retry_interval_s;
let deadline = std::time::Duration::from_millis(
attempts * (timeout_ms + 5000) + attempts.saturating_sub(1) * retry_gap_s * 1000,
);
let result = match tokio::time::timeout(deadline, run_check_with_retries(&client, &monitor, scheduled_at_iso.clone(), &region_owned, &run_id_owned)).await {
Ok(r) => r,
Err(_) => PingResult {
monitor_id: monitor.id.clone(),
@@ -113,6 +117,35 @@ pub async fn fetch_and_run(
Ok(spawned)
}
async fn run_check_with_retries(client: &reqwest::Client, monitor: &Monitor, scheduled_at: Option<String>, region: &str, run_id: &str) -> PingResult {
let attempts = monitor.max_retries.saturating_add(1);
let retry_gap = std::time::Duration::from_secs(monitor.retry_interval_s);
let mut last: Option<PingResult> = None;
for attempt in 0..attempts {
let mut result = run_check(client, monitor, scheduled_at.clone(), region, run_id).await;
if result.up {
if attempt > 0 {
if let Some(meta) = result.meta.as_mut().and_then(|m| m.as_object_mut()) {
meta.insert("retries".into(), json!(attempt));
}
}
return result;
}
last = Some(result);
if attempt + 1 < attempts {
tokio::time::sleep(retry_gap).await;
}
}
let mut result = last.expect("at least one attempt");
if attempts > 1 {
let meta = result.meta.get_or_insert_with(|| json!({}));
if let Some(obj) = meta.as_object_mut() {
obj.insert("retries".into(), json!(attempts - 1));
}
}
result
}
async fn run_check(client: &reqwest::Client, monitor: &Monitor, scheduled_at: Option<String>, region: &str, run_id: &str) -> PingResult {
let checked_at = chrono::Utc::now().to_rfc3339();
let jitter_ms: Option<i64> = scheduled_at.as_deref().and_then(|s| {
@@ -203,7 +236,7 @@ async fn run_check(client: &reqwest::Client, monitor: &Monitor, scheduled_at: Op
}
}
} else {
(status < 400, None)
((200..300).contains(&status), None)
};
let cert_expiry_days = match cert_handle {
@@ -304,15 +337,20 @@ fn run_check_blocking(
resp_headers.insert(name.as_str().to_lowercase(), value.to_str().unwrap_or("").to_string());
}
const MAX_BODY: usize = 10 * 1024 * 1024;
let body_str = match resp.body_mut().read_to_string() {
Ok(s) => s,
Err(e) => format!("[failed to read body: {}]", e),
};
let body_out = if body_str.len() > MAX_BODY {
format!("[body truncated: {} bytes]", body_str.len())
} else {
body_str
const MAX_BODY: usize = 2 * 1024 * 1024;
let body_out = match resp.body_mut().with_config().limit((MAX_BODY + 1) as u64).read_to_vec() {
Ok(mut buf) => {
if buf.len() > MAX_BODY { buf.truncate(MAX_BODY); }
String::from_utf8_lossy(&buf).into_owned()
}
Err(e) => {
let msg = e.to_string();
if msg.contains("limit") || msg.contains("Limit") {
format!("[body exceeded {}-byte cap]", MAX_BODY)
} else {
format!("[failed to read body: {}]", e)
}
}
};
Ok((status, resp_headers, body_out))
+4
View File
@@ -23,6 +23,10 @@ pub struct Monitor {
pub request_body: Option<String>,
pub timeout_ms: Option<u64>,
pub interval_s: i64,
#[serde(default)]
pub max_retries: u32,
#[serde(default)]
pub retry_interval_s: u64,
pub query: Option<Value>,
pub scheduled_at: Option<String>, // ISO string for backward compat in PingResult
#[serde(deserialize_with = "deserialize_ms")]