Skip to main content

git_policy_cli/landing/
service_index_found.rs

1//! What:
2//!  What an acquisition of the index lock found when it had to wait:
3//! the marker on the lock,
4//! how old the lock is,
5//! whether the holder it names is gone,
6//! running or unknown,
7//! and which processes have the lock open;
8//! the notes an acquisition keeps for the service's log.
9//! Why:
10//!  A commit that waited five seconds for the index lock left a log that said how long and not for whom.
11//! The description is built only after a placement found the lock held,
12//! so the fast path of an acquisition pays nothing for it;
13//! the scan of `/proc/*/fd` for the processes that have the lock open is made once,
14//! at the first look.
15//!
16//! In TS you'd write (pseudocode):
17//! ```ts
18//! const found = describeLock(lockPath, true); // 'service seq=7 pid=42 state=running age_ms=5000 open_by=[42 (cli-git-service)]'
19//! ```
20
21use crate::index_lock_holders::{Holder, HolderEvidence};
22use crate::service_index_marker::{HolderState, holder_state, marker_of};
23use std::path::Path;
24use std::time::{Duration, SystemTime};
25
26/// What an acquisition noted about its wait.
27#[derive(Clone, Debug, Default, Eq, PartialEq)]
28pub struct WaitNotes {
29    /// The words for the holder the evidence named while the lock was waited for.
30    pub holder: Option<String>,
31    /// What the first look at the held lock found.
32    pub found: Option<String>,
33    /// What the last look found,
34    /// which is the holder that let go when the wait ended in a success.
35    pub last: Option<String>,
36    /// How many times the placement found the lock held.
37    pub looks: usize,
38    /// Time the acquisition spent before its first placement getting a marked file that is flushed to storage:
39    /// the whole creation and flush when it staged its own,
40    /// the wait for the flush when the file was made ahead and was still being flushed,
41    /// and nothing to speak of when it was ready.
42    pub staging: Duration,
43    /// How the marked file came about:
44    /// `pre` when it was made ahead while the service was idle,
45    /// `cold` when the acquisition made it.
46    pub stage_kind: &'static str,
47    /// Time from the first placement to the lock being taken or given up:
48    /// the wait for a holder.
49    pub placing: Duration,
50}
51
52/// The word for what a reader makes of the holder a marker names.
53fn state_word(state: HolderState) -> &'static str {
54    return match state {
55        HolderState::Gone => "gone",
56        HolderState::Running => "running",
57        HolderState::Unknown => "unknown",
58    };
59}
60
61/// The processes that a scan saw with the lock open,
62/// as `open_by=[pid (comm), ...]`,
63/// or nothing when none was seen.
64/// Why:
65///   The name of a process is its `comm` (at most fifteen bytes,
66/// the program's name),
67/// never its arguments or its environment,
68/// which can hold a token;
69/// the scan reads nothing else,
70/// and the description is quoted when it is written into a line.
71///
72/// In TS you'd write (pseudocode):
73/// ```ts
74/// openByText(holders) // 'open_by=[42 (git)]'
75/// ```
76pub fn open_by_text(seen: Option<&HolderEvidence>) -> Option<String> {
77    let holders: &Vec<Holder> = &seen?.holders;
78    if holders.is_empty() {
79        return None;
80    }
81    let names: Vec<String> = holders
82        .iter()
83        .map(|holder| {
84            return holder.command.as_ref().map_or_else(|| { return holder.pid.to_string(); }, |command| { return format!("{} ({command})", holder.pid); });
85        })
86        .collect();
87    return Some(format!("open_by=[{}]", names.join(", ")));
88}
89
90/// What:
91///  The description of the lock at `lock` as a waiter finds it now:
92/// `vanished` when it is gone,
93/// `service seq=.. pid=.. state=.. age_ms=..` for a lock with the service's marker,
94/// and `foreign no-marker size=.. age_ms=..` for any other.
95/// Why:
96///   One line a person can read in the service's log to see what a wait was for.
97/// The marker's birth identity (the start tick,
98/// the boot ID,
99/// the PID namespace) is not in it:
100/// the state says what it was used for,
101/// and the identifiers are not needed to read the line.
102///
103/// In TS you'd write (pseudocode):
104/// ```ts
105/// describeLock(lockPath) // string
106/// ```
107pub fn describe_lock(lock: &Path) -> String {
108    let Ok(metadata) = std::fs::metadata(lock) else { return String::from("vanished") };
109    let age_ms: u128 = metadata.modified().ok().and_then(|made| { return SystemTime::now().duration_since(made).ok(); }).map_or(0, |age| { return age.as_millis(); });
110    return match marker_of(lock) {
111        Some(marked) => match marked.holder {
112            Some((pid, birth)) => format!(
113                "service seq={} pid={pid} state={} age_ms={age_ms}",
114                marked.seq,
115                state_word(holder_state(Some(&(pid, birth))))
116            ),
117            None => format!("service seq={} holder=none (an earlier release's marker) age_ms={age_ms}", marked.seq),
118        },
119        None => format!("foreign no-marker size={} age_ms={age_ms}", metadata.len()),
120    };
121}