Skip to main content

leviath_cli/daemon/
lifecycle.rs

1//! Deciding what `lev daemon start` / `stop` / `status` should do.
2//!
3//! Each function here is the *judgement* part of a subcommand: given what was
4//! observed about the daemon, what has to happen and what gets printed. The
5//! observing is left to the caller, for the reason
6//! [`readiness::poll_until`](super::readiness::poll_until) takes its predicate
7//! as an argument - the sequencing is worth testing, and only the probe needs a
8//! real socket or a real process.
9
10/// What has to happen before a daemon on the current build is running.
11///
12/// Returned rather than performed, so `main.rs` supplies the socket probe and
13/// the process spawn while the decision between these three cases stays here.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct StartSteps {
16    /// Shut down what is running before spawning.
17    ///
18    /// Only ever true alongside `spawn`: the reason to stop a daemon here is to
19    /// replace it, never to leave the machine without one.
20    pub shutdown_first: bool,
21    /// Spawn a daemon.
22    pub spawn: bool,
23}
24
25impl StartSteps {
26    /// Nothing to do; one is already up on this build.
27    const SATISFIED: Self = Self {
28        shutdown_first: false,
29        spawn: false,
30    };
31}
32
33/// What `lev` must do to reach "a daemon on the current build is running".
34///
35/// A daemon on an older build is restarted rather than reused. It cannot pick
36/// up new code, and the alternative - talking to it anyway - means a `lev` that
37/// was just rebuilt silently drives the previous build's engine. The restart is
38/// safe because the daemon reloads its persisted agents on startup, so
39/// in-flight runs survive the swap.
40///
41/// `stale` is only meaningful when `running`; a build marker left by a daemon
42/// that has since exited says nothing about what is about to be spawned.
43pub fn start_steps(running: bool, stale: bool) -> StartSteps {
44    match (running, stale) {
45        (true, false) => StartSteps::SATISFIED,
46        (true, true) => StartSteps {
47            shutdown_first: true,
48            spawn: true,
49        },
50        (false, _) => StartSteps {
51            shutdown_first: false,
52            spawn: true,
53        },
54    }
55}
56
57/// What to do when the control channel would not accept a shutdown.
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub enum StopFallback {
60    /// Signal the recorded process group instead.
61    ///
62    /// Without this a daemon that cannot be talked to cannot be stopped either,
63    /// and `lev daemon restart` - which stops before it starts - could never
64    /// recover one that had wedged.
65    Signal(u32),
66    /// Nothing recorded a pid, so the control-channel error is the whole story
67    /// and is reported as-is rather than replaced by a vaguer one.
68    Propagate,
69}
70
71/// Decide the fallback for a refused shutdown, given the recorded pid.
72pub fn stop_fallback(recorded_pid: Option<u32>) -> StopFallback {
73    match recorded_pid {
74        Some(pid) => StopFallback::Signal(pid),
75        None => StopFallback::Propagate,
76    }
77}
78
79/// The line `lev daemon stop` prints, or the error it fails with.
80///
81/// `was_running` and `stopped` are separate observations because they answer
82/// different questions and the pair "nothing was running" and "it did not stop"
83/// must not read the same: the first is a success.
84pub fn stop_outcome(was_running: bool, stopped: bool) -> Result<&'static str, &'static str> {
85    match (was_running, stopped) {
86        (false, _) => Ok("daemon not running"),
87        (true, true) => Ok("daemon stopped"),
88        (true, false) => Err("the leviath daemon did not shut down within 5s"),
89    }
90}
91
92/// The lines `lev daemon status` prints.
93///
94/// `supervision` is `None` on a platform with no supported supervisor, where
95/// there is genuinely nothing to report - as distinct from a supported platform
96/// with nothing installed, which says so.
97pub fn status_lines(running: bool, agents: usize, supervision: Option<String>) -> Vec<String> {
98    let mut lines = vec![crate::commands::daemon::format_status(running, agents)];
99    lines.extend(supervision);
100    lines
101}
102
103#[cfg(test)]
104mod tests;