use std::sync::Arc;
use std::time::{Duration, Instant};
use crate::daemon::rpc::core_ops;
use crate::daemon::state::DaemonState;
use crate::session_manager::{ManagedError, ManagedTmuxDriver};
const HEALTH_BUDGET: Duration = Duration::from_millis(250);
struct WedgedTmux;
impl ManagedTmuxDriver for WedgedTmux {
fn create_session(&self, _name: &str, _workdir: &str) -> Result<(), ManagedError> {
Ok(())
}
fn kill_session(&self, _name: &str) -> Result<(), ManagedError> {
Ok(())
}
fn send_line(&self, _name: &str, _text: &str) -> Result<(), ManagedError> {
Ok(())
}
fn capture(&self, _name: &str, _lines: usize) -> Result<String, ManagedError> {
Ok(String::new())
}
fn list_sessions(&self) -> Result<Vec<String>, ManagedError> {
std::thread::sleep(Duration::from_secs(10));
Ok(Vec::new())
}
}
async fn worst_health_latency_during<F>(state: &Arc<DaemonState>, work: F) -> Duration
where
F: std::future::Future<Output = ()>,
{
let probe_state = Arc::clone(state);
let done = Arc::new(std::sync::atomic::AtomicBool::new(false));
let probe_done = Arc::clone(&done);
let prober = tokio::spawn(async move {
let mut worst = Duration::ZERO;
while !probe_done.load(std::sync::atomic::Ordering::Relaxed) {
let t0 = Instant::now();
let _ = core_ops::health(&probe_state).await;
worst = worst.max(t0.elapsed());
tokio::time::sleep(Duration::from_millis(20)).await;
}
worst
});
work.await;
done.store(true, std::sync::atomic::Ordering::Relaxed);
prober.await.expect("the health prober must not panic")
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_wedged_liveness_probe_neither_hangs_the_sweep_nor_delays_health() {
let dir = tempfile::tempdir().expect("scratch framework root");
let state = Arc::new(
DaemonState::with_root_isolated_managed_and_driver(
dir.path().to_path_buf(),
Arc::new(WedgedTmux),
)
.await,
);
let mgr = state.session_manager().await;
let started = Instant::now();
let worst = worst_health_latency_during(&state, async {
let _ = mgr.workspace_claims(None).await;
})
.await;
let elapsed = started.elapsed();
assert!(
elapsed < Duration::from_secs(6),
"the liveness probe must bound itself; the pass took {elapsed:?} (#7965)"
);
assert!(
worst < HEALTH_BUDGET,
"/health took {worst:?} while the sweep probed tmux; budget is {HEALTH_BUDGET:?} (#7965)"
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_wedged_hygiene_fetch_neither_hangs_the_sweep_nor_delays_health() {
use crate::daemon::managed_routes::inproject_hygiene;
let dir = tempfile::tempdir().expect("scratch root");
let repos_root = dir.path().join("repos");
let base = repos_root.join("owner").join("repo");
std::fs::create_dir_all(&base).expect("base clone");
for args in [
vec!["init", "-q"],
vec!["remote", "add", "origin", "git://10.255.255.1:9418/x.git"],
] {
let out = trusty_common::git::command_in(&base)
.args(&args)
.output()
.expect("git must be available to set the fixture up");
assert!(out.status.success(), "git {args:?}: {out:?}");
}
assert!(
!base.join(".git").join("FETCH_HEAD").exists(),
"the fixture must be due a fetch"
);
let state = Arc::new(DaemonState::with_root(dir.path().to_path_buf()));
let started = Instant::now();
let worst = worst_health_latency_during(&state, async {
let root = repos_root.clone();
tokio::task::spawn_blocking(move || {
inproject_hygiene::run_hygiene_for_all_bases_within(
&root,
Duration::from_secs(60),
Duration::from_secs(2),
);
})
.await
.expect("the hygiene pass must not panic");
})
.await;
let elapsed = started.elapsed();
assert!(
elapsed < Duration::from_secs(30),
"a blocked fetch must be killed, not waited on; the pass took {elapsed:?} (#7965)"
);
assert!(
worst < HEALTH_BUDGET,
"/health took {worst:?} while hygiene ran; budget is {HEALTH_BUDGET:?} (#7965)"
);
}