1use std::sync::atomic::{AtomicBool, Ordering};
14use std::sync::{Arc, Mutex};
15use std::time::Duration;
16
17const PULSE: Duration = Duration::from_secs(60);
18
19pub struct Heartbeat {
21 phase: Arc<Mutex<String>>,
22 stop: Arc<AtomicBool>,
23 handle: Option<std::thread::JoinHandle<()>>,
24}
25
26impl Heartbeat {
27 pub fn start() -> Self {
28 let phase = Arc::new(Mutex::new(String::from("starting")));
29 let stop = Arc::new(AtomicBool::new(false));
30
31 let ticker_phase = Arc::clone(&phase);
32 let ticker_stop = Arc::clone(&stop);
33 let handle = std::thread::spawn(move || {
34 let mut waited = Duration::ZERO;
37 while !ticker_stop.load(Ordering::Relaxed) {
38 std::thread::sleep(Duration::from_millis(200));
39 waited += Duration::from_millis(200);
40 if waited >= PULSE {
41 waited = Duration::ZERO;
42 let current = ticker_phase
43 .lock()
44 .map(|p| p.clone())
45 .unwrap_or_else(|_| "unknown".into());
46 println!(">> …still in: {current}");
47 }
48 }
49 });
50
51 Self {
52 phase,
53 stop,
54 handle: Some(handle),
55 }
56 }
57
58 pub fn phase(&self, name: &str) {
60 println!(">> {name}");
61 if let Ok(mut phase) = self.phase.lock() {
62 *phase = name.to_owned();
63 }
64 }
65}
66
67impl Drop for Heartbeat {
68 fn drop(&mut self) {
69 self.stop.store(true, Ordering::Relaxed);
70 if let Some(handle) = self.handle.take() {
71 let _ = handle.join();
72 }
73 }
74}