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