use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
use super::super::*;
use super::{hold, is_held, release};
use crate::stores::ServerStores;
fn wait_for(what: &str, mut cond: impl FnMut() -> bool) {
let deadline = Instant::now() + Duration::from_secs(20);
while !cond() {
assert!(Instant::now() < deadline, "timed out waiting for: {what}");
std::thread::sleep(Duration::from_millis(5));
}
}
#[test]
fn a_held_twin_applies_nothing_says_maintenance_and_follows_again_when_released() {
let pd = tempfile::tempdir().unwrap();
let td = tempfile::tempdir().unwrap();
let primary = Arc::new(ServerStores::open(pd.path()).unwrap());
primary.catalog().create_namespace("before").unwrap();
TwinConfig { primary: "primary.test:50051".into(), joined_at_unix_ms: 0 }.save(td.path()).unwrap();
let twin = Arc::new(ServerStores::open(td.path()).unwrap());
let stop = Arc::new(AtomicBool::new(false));
let runner = {
let (twin, log, stop) = (Arc::clone(&twin), Arc::clone(primary.txlog()), Arc::clone(&stop));
std::thread::spawn(move || Follower::new(twin, Box::new(log)).run(Duration::from_millis(5), &stop))
};
wait_for("the twin level with its primary", || twin.txlog().head_seq() == primary.txlog().head_seq());
hold(twin.root());
assert!(is_held(twin.root()));
let held_at = twin.txlog().head_seq();
let s = status(twin.root()).expect("a status");
assert_eq!(s.state, State::Maintenance);
assert_eq!(s.state.to_string(), "maintenance");
let report = measure_report(&twin);
assert_eq!(report.state, "maintenance", "the word the primary's heartbeat carries");
assert_eq!((report.disk_total_bytes, report.disk_used_bytes), (0, 0), "an unmounted set has no size to report");
primary.catalog().create_namespace("during").unwrap();
let primary_head = primary.txlog().head_seq();
assert!(primary_head > held_at);
wait_for("the held twin to hear the primary's new head", || status(twin.root()).is_some_and(|s| s.primary_head_seq == primary_head));
std::thread::sleep(Duration::from_millis(60));
assert_eq!(twin.txlog().head_seq(), held_at, "a held twin appended nothing");
assert!(twin.catalog().namespace("during").unwrap().is_none(), "a held twin applied nothing");
let s = status(twin.root()).unwrap();
assert_eq!(s.state, State::Maintenance, "still maintenance, with the lag visible: {s:?}");
assert!(!s.caught_up());
release(twin.root());
wait_for("the released twin level again", || twin.txlog().head_seq() == primary_head);
wait_for("the status back to following", || status(twin.root()).is_some_and(|s| s.state == State::Following));
assert!(twin.catalog().namespace("during").unwrap().is_some(), "what was written during the growth is on the twin now");
assert!(status(twin.root()).unwrap().caught_up());
stop.store(true, Ordering::SeqCst);
runner.join().unwrap();
}
#[test]
fn a_held_twin_says_maintenance_even_without_a_running_follower() {
let td = tempfile::tempdir().unwrap();
TwinConfig { primary: "primary.test:50051".into(), joined_at_unix_ms: 0 }.save(td.path()).unwrap();
let twin = Arc::new(ServerStores::open(td.path()).unwrap());
hold(twin.root());
stalled(&twin, 1, "the primary did not answer", Duration::from_secs(5));
assert_eq!(status(twin.root()).unwrap().state, State::Maintenance);
release(twin.root());
assert!(!is_held(twin.root()));
}