huskies: merge 1156 story Periodic liveness tick so runtime freezes have a precise timestamp

This commit is contained in:
dave
2026-05-20 01:18:59 +00:00
parent 7be3bf1dbf
commit 2fb935e726
4 changed files with 43 additions and 0 deletions
+10
View File
@@ -167,6 +167,11 @@ pub struct WatcherConfig {
/// state diverged. Default: 30 seconds. /// state diverged. Default: 30 seconds.
#[serde(default = "default_reconcile_interval_secs")] #[serde(default = "default_reconcile_interval_secs")]
pub reconcile_interval_secs: u64, pub reconcile_interval_secs: u64,
/// How often (in seconds) the liveness tick task emits a heartbeat log line.
/// Reduce if 5-second granularity causes noise; increase to quiet the logs.
/// Default: 5 seconds.
#[serde(default = "default_liveness_tick_secs")]
pub liveness_tick_secs: u64,
} }
impl Default for WatcherConfig { impl Default for WatcherConfig {
@@ -175,6 +180,7 @@ impl Default for WatcherConfig {
sweep_interval_secs: default_sweep_interval_secs(), sweep_interval_secs: default_sweep_interval_secs(),
done_retention_secs: default_done_retention_secs(), done_retention_secs: default_done_retention_secs(),
reconcile_interval_secs: default_reconcile_interval_secs(), reconcile_interval_secs: default_reconcile_interval_secs(),
liveness_tick_secs: default_liveness_tick_secs(),
} }
} }
} }
@@ -191,6 +197,10 @@ fn default_reconcile_interval_secs() -> u64 {
30 30
} }
fn default_liveness_tick_secs() -> u64 {
5
}
fn default_qa() -> String { fn default_qa() -> String {
"server".to_string() "server".to_string()
} }
+9
View File
@@ -403,6 +403,15 @@ async fn main() -> Result<(), std::io::Error> {
Some(event_buffer), Some(event_buffer),
); );
// Permanent liveness heartbeat — stops when the tokio runtime freezes,
// giving a precise timestamp of the freeze from the last [liveness] line.
let liveness_tick_secs = startup_root
.as_ref()
.and_then(|r| config::ProjectConfig::load(r).ok())
.map(|c| c.watcher.liveness_tick_secs)
.unwrap_or(5);
startup::tick_loop::spawn_liveness_tick(liveness_tick_secs);
// Unified 1-second background tick loop. // Unified 1-second background tick loop.
startup::tick_loop::spawn_tick_loop( startup::tick_loop::spawn_tick_loop(
Arc::clone(&startup_agents), Arc::clone(&startup_agents),
+1
View File
@@ -192,6 +192,7 @@ mod tests {
sweep_interval_secs: 30, sweep_interval_secs: 30,
done_retention_secs: 7200, done_retention_secs: 7200,
reconcile_interval_secs: 30, reconcile_interval_secs: 30,
liveness_tick_secs: 5,
}, },
..Default::default() ..Default::default()
}; };
+23
View File
@@ -226,6 +226,29 @@ pub(crate) fn spawn_tick_loop(
}); });
} }
/// Spawn a permanent background task that emits a liveness heartbeat log line
/// every `interval_secs` seconds on the main tokio runtime.
///
/// Running on the main runtime (not `spawn_blocking`) means the heartbeat stops
/// the moment the runtime freezes, giving an exact timestamp of the freeze from
/// the last `[liveness]` line in the logs.
pub(crate) fn spawn_liveness_tick(interval_secs: u64) {
let interval_secs = interval_secs.max(1);
crate::slog!("[liveness] liveness tick started; cadence={interval_secs}s");
tokio::spawn(async move {
let duration = std::time::Duration::from_secs(interval_secs);
let mut ticker = tokio::time::interval(duration);
loop {
ticker.tick().await;
let ts = std::time::UNIX_EPOCH
.elapsed()
.unwrap_or_default()
.as_secs();
crate::slog!("[liveness] tokio runtime alive ts={ts}");
}
});
}
/// Fire any due generic scheduled timers and re-arm recurring ones. /// Fire any due generic scheduled timers and re-arm recurring ones.
/// ///
/// Called every second from the unified tick loop. Catch-up semantics: timers /// Called every second from the unified tick loop. Catch-up semantics: timers