Skip to main content

git_policy_cli/
index_lock_wait_host.rs

1//! What:
2//!  The host side effects of the index-lock wait:
3//!  `/proc` and Git-free lock reads,
4//!  real sleeps,
5//!       operating-system randomness and standard error.
6//! Why:
7//!  `WaitEffects` is the seam the controls replace;
8//!  this is what the executable runs.
9//!
10//! In TS you'd write (pseudocode):
11//! ```ts
12//! const HOST_WAIT_EFFECTS = { gather, sleep: wait, random: Math.random, report: stderr.write, now: performance.now };
13//! ```
14
15/// Foreign-lock evidence readers used by the host.
16use super::index_lock_evidence::{Evidence, gather_index_lock_evidence, read_lock_metadata};
17/// The wait's side-effect seam.
18use super::index_lock_wait::WaitEffects;
19/// `Path` and `PathBuf` are borrowed and owned filesystem paths.
20use std::path::{Path, PathBuf};
21/// `Instant` is a monotonic clock reading.
22use std::time::{Duration, Instant};
23
24/// What:
25///  The host effects for one wait on one real index.
26/// Why:
27///   The clock starts when the wait does,
28///  so `now_ms` measures the wait's own time.
29///
30/// In TS you'd write (pseudocode):
31/// ```ts
32/// const effects = { ...HOST_WAIT_EFFECTS, now: () => performance.now() - start };
33/// ```
34pub struct HostWaitEffects {
35    /// The worktree's real index;
36    ///  the lock is `<real index>.lock`.
37    pub real_index: PathBuf,
38    /// When the wait started.
39    pub started: Instant,
40}
41
42/// What:
43///  Turn an I/O error into the text the wait reports.
44/// Why:
45///   A named function keeps the conversion free of closures.
46///
47/// In TS you'd write (pseudocode):
48/// ```ts
49/// (error: Error) => error.message
50/// ```
51fn io_text(error: std::io::Error) -> String {
52    return error.to_string();
53}
54
55/// What:
56///  The lock path of a real index.
57/// Why:
58///   Git names the lock `<index>.lock` beside the index.
59///
60/// In TS you'd write (pseudocode):
61/// ```ts
62/// `${realIndexPath}.lock`
63/// ```
64pub fn lock_path_of(real_index: &Path) -> PathBuf {
65    let mut name: std::ffi::OsString = real_index.as_os_str().to_owned();
66    name.push(".lock");
67    return PathBuf::from(name);
68}
69
70impl WaitEffects for HostWaitEffects {
71    fn lock_present(&mut self) -> Result<bool, String> {
72        let lock: PathBuf = lock_path_of(self.real_index.as_path());
73        match read_lock_metadata(lock.as_path()) {
74            Ok(Some(_)) => return Ok(true),
75            Ok(None) => return Ok(false),
76            Err(error) => return Err(io_text(error)),
77        }
78    }
79
80    fn gather(&mut self) -> Result<Option<Evidence>, String> {
81        match gather_index_lock_evidence(self.real_index.as_path()) {
82            Ok(found) => return Ok(found),
83            Err(error) => return Err(io_text(error)),
84        }
85    }
86
87    fn sleep(&mut self, ms: u64) {
88        std::thread::sleep(Duration::from_millis(ms));
89    }
90
91    fn random(&mut self) -> f64 {
92        let mut bytes: [u8; 4] = [0; 4];
93        // A failed operating-system random source falls back to the middle of the range,
94        // which keeps the schedule's expected value.
95        if getrandom::fill(&mut bytes).is_err() {
96            return 0.5;
97        }
98        return f64::from(u32::from_le_bytes(bytes)) / 4_294_967_296.0;
99    }
100
101    fn now_ms(&mut self) -> f64 {
102        return self.started.elapsed().as_secs_f64() * 1000.0;
103    }
104
105    fn report(&mut self, line: &str) {
106        use std::io::Write;
107        // A standard-error write failure cannot be reported anywhere else, so it is dropped.
108        let _ = std::io::stderr().write_all(line.as_bytes());
109    }
110}
111
112#[cfg(test)]
113#[path = "index_lock_wait_host_tests.rs"]
114mod tests;