char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somrearrange libs into lib and extensions into extsf4a0f5b

main
3.7 KiB119 linesraw
1use std::io;
2use std::sync::Mutex;
3use std::time::{Duration, Instant};
4
5use crate::{Child, Group, Launch};
6
7pub(crate) struct Escalation {
8    steps: Vec<(String, Duration)>,
9    next: usize,
10    deadline: Option<Instant>,
11    pub forced: bool,
12}
13impl Escalation {
14    pub fn new(mut steps: Vec<(String, Duration)>) -> Self {
15        steps.push(("SIGKILL".into(), Duration::from_secs(5)));
16        Self {
17            steps,
18            next: 0,
19            deadline: None,
20            forced: false,
21        }
22    }
23    pub fn cancelled(&self) -> bool {
24        self.deadline.is_some()
25    }
26    pub fn request(&mut self, force: bool) -> bool {
27        let changed = !self.cancelled() || (force && !self.forced);
28        self.deadline.get_or_insert_with(Instant::now);
29        self.forced |= force;
30        changed
31    }
32    pub fn advance(&mut self, mut signal: impl FnMut(&str) -> io::Result<bool>) -> io::Result<()> {
33        let Some(mut deadline) = self.deadline else {
34            return Ok(());
35        };
36        let mut error = None;
37        if self.forced {
38            self.next = self.next.max(self.steps.len() - 1);
39        }
40        while self.next < self.steps.len() && (self.forced || Instant::now() >= deadline) {
41            let (name, grace) = &self.steps[self.next];
42            if let Err(e) = signal(name) {
43                error.get_or_insert(e);
44            }
45            self.next += 1;
46            deadline = Instant::now() + *grace;
47        }
48        self.deadline = Some(deadline);
49        error.map_or(Ok(()), Err)
50    }
51    fn expired(&self) -> bool {
52        self.next == self.steps.len() && self.deadline.is_some_and(|at| Instant::now() >= at)
53    }
54}
55
56/// An explicitly retained scope: command exits do not end descendant ownership.
57/// Cancellation serializes with both launching and `if_running` publication.
58pub struct Scope {
59    group: Group,
60    stop: Mutex<Escalation>,
61}
62impl Scope {
63    pub fn new(host: &std::path::Path, steps: Vec<(String, Duration)>) -> io::Result<Self> {
64        Ok(Self {
65            group: Group::new(host)?,
66            stop: Mutex::new(Escalation::new(steps)),
67        })
68    }
69    pub fn is_cancelled(&self) -> bool {
70        self.stop.lock().unwrap().cancelled()
71    }
72    pub fn if_running<T>(&self, action: impl FnOnce() -> T) -> Option<T> {
73        let stop = self.stop.lock().unwrap();
74        if stop.cancelled() {
75            None
76        } else {
77            Some(action())
78        }
79    }
80    pub(crate) fn spawn(&self, launch: Launch<'_>) -> io::Result<Child> {
81        let stop = self.stop.lock().unwrap();
82        if stop.cancelled() {
83            return Err(io::Error::new(
84                io::ErrorKind::Interrupted,
85                "Task run was cancelled",
86            ));
87        }
88        crate::spawn(Launch {
89            join: Some(&self.group),
90            group: false,
91            ..launch
92        })
93    }
94    pub fn cancel(&self) {
95        self.stop.lock().unwrap().request(false);
96    }
97    /// Bounded, resumable shutdown. The caller can include its own work in settling.
98    pub fn terminate(&self, settled: impl Fn() -> bool) -> bool {
99        self.cancel();
100        loop {
101            {
102                let mut stop = self.stop.lock().unwrap();
103                if !self.group.alive() && settled() {
104                    self.group.close();
105                    return true;
106                }
107                let _ = stop.advance(|signal| self.group.signal(signal));
108                if stop.expired() {
109                    return false;
110                }
111            }
112            std::thread::sleep(Duration::from_millis(5));
113        }
114    }
115    pub fn force_close(&self) {
116        self.stop.lock().unwrap().request(true);
117        self.group.close();
118    }
119}