supercov_engine/
progress.rs1use std::{
12 io::IsTerminal,
13 sync::{
14 Arc,
15 atomic::{AtomicBool, Ordering},
16 },
17 thread::JoinHandle,
18};
19#[cfg(unix)]
20use std::{io::Write, time::Duration};
21
22#[cfg(unix)]
25const QUIET_PERIOD: Duration = Duration::from_millis(120);
26
27#[cfg(unix)]
33fn status_output() -> Option<std::fs::File> {
34 use std::os::fd::FromRawFd;
35 let descriptor = unsafe { libc::dup(2) };
36 (descriptor >= 0).then(|| unsafe { std::fs::File::from_raw_fd(descriptor) })
37}
38
39pub struct ProgressLine {
40 stop: Arc<AtomicBool>,
41 handle: Option<JoinHandle<()>>,
42}
43
44impl ProgressLine {
45 pub fn start(message: &'static str) -> Option<Self> {
46 if cfg!(not(unix)) || !std::io::stderr().is_terminal() {
47 return None;
48 }
49 Self::start_on_terminal(message)
50 }
51
52 #[cfg(unix)]
53 fn start_on_terminal(message: &'static str) -> Option<Self> {
54 let mut output = status_output()?;
55 let stop = Arc::new(AtomicBool::new(false));
56 let flag = Arc::clone(&stop);
57 let handle = std::thread::spawn(move || {
58 std::thread::sleep(QUIET_PERIOD);
59 if flag.load(Ordering::Relaxed) {
60 return;
61 }
62 let _ = writeln!(output, "❋ {message}…");
63 });
64 Some(Self {
65 stop,
66 handle: Some(handle),
67 })
68 }
69
70 #[cfg(not(unix))]
71 fn start_on_terminal(_message: &'static str) -> Option<Self> {
72 None
75 }
76}
77
78impl Drop for ProgressLine {
79 fn drop(&mut self) {
80 self.stop.store(true, Ordering::Relaxed);
81 if let Some(handle) = self.handle.take() {
82 let _ = handle.join();
83 }
84 }
85}
86
87#[cfg(all(test, unix))]
88mod tests {
89 use super::*;
90
91 #[test]
97 fn status_line_never_needs_stds_stderr_lock() {
98 let diagnostics = std::io::stderr().lock();
99 let line = ProgressLine::start_on_terminal("proving the status line stays lock-free");
100 std::thread::sleep(QUIET_PERIOD * 2);
101 let (sender, receiver) = std::sync::mpsc::channel();
102 std::thread::spawn(move || {
103 drop(line);
104 let _ = sender.send(());
105 });
106 let dropped = receiver.recv_timeout(Duration::from_secs(10));
107 drop(diagnostics);
108 assert!(
109 dropped.is_ok(),
110 "dropping the status line deadlocked against std's stderr lock"
111 );
112 }
113}