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