chj_unix_util/forking_loop.rs
1use std::fmt::Display;
2
3use anyhow::{bail, Result};
4
5use crate::{
6 backoff::LoopWithBackoff,
7 unix::{easy_fork, waitpid_until_gone, Status},
8};
9
10/// Runs `job` repeatedly forever by forking off a child, running the
11/// `job` in the child once. In the parent, wait for the child to end,
12/// then treat both error returns and crashes / non-0 exits as errors.
13/// Restart but back off before retrying, using the given
14/// `LoopWithBackoff` config. Runs `until` after every run, and
15/// returns if it returns true.
16///
17/// Note: must be run while there are no running threads, panics
18/// otherwise!
19pub fn forking_loop<E: Display, F: FnOnce() -> Result<(), E>>(
20 config: LoopWithBackoff,
21 job: F,
22 until: impl Fn() -> bool,
23) where
24 anyhow::Error: From<E>,
25{
26 let mut perhaps_job = Some(job);
27 config.run(
28 || -> Result<()> {
29 if let Some(pid) = easy_fork()? {
30 // Parent process
31
32 // XXX todo: optionally set up a thread that kills the
33 // pid after a timeout.
34
35 match waitpid_until_gone(pid)? {
36 Status::Normalexit(code) => {
37 if code != 0 {
38 bail!("child {pid} exited with exit code {code}");
39 }
40 }
41 Status::Signalexit(signal) => {
42 bail!("child {pid} terminated by signal {signal}");
43 }
44 }
45 } else {
46 // Detach from the parent, type system wise at least,
47 // to allow for `job` to be FnOnce.
48 let mut perhaps_job = unsafe {
49 // Safety: Uh, not very sure at all. It's as if
50 // each child created the same state from scratch,
51 // with everything attached. There is no safe
52 // shared memory, so no such problems (there are
53 // *some* safe(?) shared unix resources across
54 // fork, though, like flock, XX todo.).
55 // Check:
56 // https://users.rust-lang.org/t/moving-borrowed-values-into-a-forked-child/28183
57 ((&mut perhaps_job) as *const Option<F>).read()
58 };
59 let job = perhaps_job.take().expect("only once per child");
60
61 // Child process
62 match job() {
63 Ok(()) => std::process::exit(0),
64 Err(e) => {
65 eprintln!("Error: {e:#}");
66 std::process::exit(1);
67 }
68 }
69 }
70 Ok(())
71 },
72 until,
73 )
74}