Skip to main content

conmonrs/
child_reaper.rs

1//! Child process reaping and management.
2use crate::{
3    attach::StopOutput,
4    container_io::{ContainerIO, SharedContainerIO},
5    mutex::lock,
6    process::{Process, pidfd_open},
7};
8use anyhow::{Context, Result, bail};
9use nix::{errno::Errno, sys::signal::Signal, unistd::Pid};
10use std::{
11    collections::{BTreeMap, HashMap, HashSet},
12    path::PathBuf,
13    pin::pin,
14    str,
15    sync::{
16        Arc, Mutex,
17        atomic::{AtomicBool, Ordering},
18    },
19};
20use tokio::{
21    sync::{Notify, broadcast::Receiver, watch},
22    time::{self, Duration, Instant},
23};
24use tokio_util::sync::CancellationToken;
25use tracing::{debug, debug_span, info};
26
27mod orphan_sweep;
28mod runtime_call;
29mod watcher;
30
31pub(crate) use runtime_call::{RuntimeCall, StartedChild};
32
33/// What kind of process a child is. Execs are registered under the id of
34/// their container, but only the container itself is the target of
35/// container level operations like attach or log reopening.
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37pub(crate) enum ChildKind {
38    Container,
39    Exec,
40}
41
42/// A container or exec, as it gets registered with the child reaper.
43#[derive(Debug)]
44pub(crate) struct Child {
45    pub(crate) id: Box<str>,
46    pub(crate) kind: ChildKind,
47    pub(crate) pid: Pid,
48    pub(crate) exit_paths: Vec<PathBuf>,
49    pub(crate) oom_exit_paths: Vec<PathBuf>,
50    pub(crate) timeout: Option<Instant>,
51    pub(crate) io: SharedContainerIO,
52    pub(crate) token: CancellationToken,
53
54    /// The process, if the caller holds it already, like an exec, so that
55    /// the registration does not open a second pidfd for it. The
56    /// registration opens it otherwise.
57    pub(crate) process: Option<Arc<Process>>,
58}
59
60/// Watches the containers and execs of the server until they exit and
61/// reaps them.
62#[derive(Debug, Default)]
63pub(crate) struct ChildReaper {
64    /// The registered children, each until its watcher ended.
65    grandchildren: Arc<Mutex<HashMap<Box<str>, Vec<ReapableChild>>>>,
66
67    /// Notified whenever a watcher ended and forgot its child, which the
68    /// shutdown waits for.
69    forgotten: Arc<Notify>,
70
71    /// The runtimes and container processes of calls in progress, which are
72    /// not registered as grandchildren yet. Their exit status belongs to the
73    /// call, so the orphan sweep leaves them alone. See [`PendingPid`].
74    pending: PendingPids,
75
76    /// Wakes the orphan sweep up early, for example after a failed runtime
77    /// call or on shutdown.
78    sweep: Notify,
79
80    /// Set by the shutdown while it holds the lock of the grandchildren, so a
81    /// child registered later knows that the shutdown missed it.
82    shutting_down: AtomicBool,
83
84    /// The runtime calls in progress, including the registration of their
85    /// child, which the shutdown waits for.
86    in_flight: Arc<watch::Sender<Calls>>,
87
88    /// Cancelled by the shutdown once the runtime calls in progress did not
89    /// end within [`ChildReaper::IN_FLIGHT_WAIT`]. A call whose runtime
90    /// still runs fails then and kills it on its own, which only the call
91    /// can do safely, see [`ChildReaper::kill_pending`].
92    stop_calls: CancellationToken,
93}
94
95/// Counts a runtime call as in progress until dropped. A successful
96/// [`ChildReaper::create_child`] hands it to the caller, which passes it to
97/// [`ChildReaper::watch_grandchild`], so the call ends with the registration
98/// of its child, or wherever the caller gives up.
99#[derive(Debug)]
100#[cfg_attr(test, derive(Default))]
101pub(crate) struct InFlight {
102    calls: Option<Arc<watch::Sender<Calls>>>,
103
104    /// The number of the call, see [`Calls`].
105    seq: u64,
106
107    /// Keeps the process which the call started pending until the call
108    /// ends, also if the call gets dropped before its registration, see
109    /// [`InFlight::claim`].
110    pending: Option<PendingPid>,
111
112    /// Cancelled once the shutdown stops the call, see
113    /// [`ChildReaper::stop_calls`].
114    stop: CancellationToken,
115}
116
117impl InFlight {
118    /// Mark the process `pid` which the call started as pending until the
119    /// call ends, and record it for the shutdown, which kills it if the
120    /// call does not end in time, see [`ChildReaper::kill_pending`]. It is
121    /// pending before it gets recorded. A shutdown which did not see the
122    /// record stopped the call before it looked at the calls, so the call
123    /// sees that here and kills the process itself.
124    fn claim(&mut self, pending: &PendingPids, pid: Pid) {
125        self.pending = Some(PendingPid::new(pending, pid));
126        if let Some(calls) = &self.calls {
127            calls.send_if_modified(|calls| {
128                if let Some(call) = calls.in_progress.get_mut(&self.seq) {
129                    call.process = Some(pid);
130                }
131                // Nobody waits for it.
132                false
133            });
134        }
135        if self.stop.is_cancelled() {
136            debug!(%pid, "Killing process of a runtime call stopped by the shutdown");
137            kill_grandchild(pid, Signal::SIGKILL);
138        }
139    }
140}
141
142impl Drop for InFlight {
143    fn drop(&mut self) {
144        if let Some(calls) = &self.calls {
145            calls.send_modify(|calls| {
146                calls.in_progress.remove(&self.seq);
147            });
148        }
149    }
150}
151
152/// The runtime calls in progress, numbered in the order they started.
153#[derive(Debug, Default)]
154struct Calls {
155    /// The number of the next call.
156    next: u64,
157
158    /// The calls in progress by their number.
159    in_progress: BTreeMap<u64, Call>,
160}
161
162/// A runtime call in progress, see [`Calls`].
163#[derive(Debug)]
164struct Call {
165    /// Until when the call may still claim a process: the deadline of its
166    /// runtime and the [`ChildReaper::CLAIM_MARGIN`].
167    claim_by: Instant,
168
169    /// The process which the call started, once it claimed it, see
170    /// [`InFlight::claim`].
171    process: Option<Pid>,
172}
173
174impl Calls {
175    /// Whether a call numbered below `before` is in progress, which may
176    /// still claim a process at `now`. The deadlines of the calls differ, an
177    /// older one may still claim one while a younger one cannot anymore.
178    fn may_claim_before(&self, before: u64, now: Instant) -> bool {
179        self.in_progress
180            .range(..before)
181            .any(|(_, call)| now < call.claim_by)
182    }
183
184    /// Whether no call is in progress.
185    fn ended(&self) -> bool {
186        self.in_progress.is_empty()
187    }
188}
189
190/// A zombie which nobody claimed, see [`ChildReaper::sweep_orphans`].
191#[derive(Clone, Copy, Debug)]
192struct Candidate {
193    first_seen: Instant,
194
195    /// The calls numbered below this started before it was first seen. It
196    /// may be the process of one of them, which may still claim it.
197    calls_before: u64,
198}
199
200/// The pending PIDs with the number of their marks, see [`PendingPid`].
201type PendingPids = Arc<Mutex<HashMap<Pid, usize>>>;
202
203/// Marks a PID as pending for the lifetime of the guard. The marks get
204/// counted: a call keeps the mark of its runtime until it reaped it, and the
205/// PID of a runtime which got reaped may already be in use by the process of
206/// another call, whose mark must stay.
207#[derive(Debug)]
208struct PendingPid {
209    pending: PendingPids,
210    pid: Pid,
211}
212
213impl PendingPid {
214    fn new(pending: &PendingPids, pid: Pid) -> Self {
215        *lock(pending).entry(pid).or_default() += 1;
216        Self {
217            pending: pending.clone(),
218            pid,
219        }
220    }
221}
222
223impl Drop for PendingPid {
224    fn drop(&mut self) {
225        let mut pending = lock(&self.pending);
226        if let Some(marks) = pending.get_mut(&self.pid) {
227            *marks -= 1;
228            if *marks == 0 {
229                pending.remove(&self.pid);
230            }
231        }
232    }
233}
234
235impl ChildReaper {
236    /// Retrieve the container registered under `id`. Execs share the id of
237    /// their container and are skipped. The error starts with
238    /// [`CONTAINER_NOT_FOUND`] if there is none.
239    pub(crate) fn get(&self, id: &str) -> Result<RegisteredContainer> {
240        lock(&self.grandchildren)
241            .get(id)
242            .and_then(|v| v.iter().find_map(ReapableChild::container))
243            .with_context(|| format!("{CONTAINER_NOT_FOUND}: {id}"))
244    }
245
246    /// Start a runtime call whose runtime gets killed at the `deadline`,
247    /// unless the server shuts down. The shutdown waits for the calls in
248    /// progress, see [`ChildReaper::kill_grandchildren`].
249    fn begin_call(&self, deadline: Instant) -> Result<InFlight> {
250        // Under the lock the shutdown takes its snapshot with, so that it
251        // either sees this call or this call sees the shutdown.
252        let _map = lock(&self.grandchildren);
253        if self.shutting_down.load(Ordering::SeqCst) {
254            bail!("server is shutting down")
255        }
256        // A deadline too far in the future to add the margin to is never
257        // reached either.
258        let claim_by = deadline.checked_add(Self::CLAIM_MARGIN).unwrap_or(deadline);
259        let mut seq = 0;
260        self.in_flight.send_modify(|calls| {
261            seq = calls.next;
262            calls.next += 1;
263            calls.in_progress.insert(
264                seq,
265                Call {
266                    claim_by,
267                    process: None,
268                },
269            );
270        });
271        Ok(InFlight {
272            calls: Some(self.in_flight.clone()),
273            seq,
274            pending: None,
275            stop: self.stop_calls.clone(),
276        })
277    }
278
279    /// Register and watch a child which `create_child` started, which ends
280    /// its call.
281    pub(crate) fn watch_grandchild(
282        &self,
283        child: Child,
284        call: InFlight,
285    ) -> Result<Receiver<ExitChannelData>> {
286        let pid = child.pid;
287        let (id, reapable_grandchild, mut exit) = ReapableChild::from_child(child);
288        let seq = reapable_grandchild.seq;
289
290        let mut map = lock(&self.grandchildren);
291        // A child registered after the shutdown took its snapshot, under this
292        // lock, is in the map as well, which the shutdown waits to empty. Its
293        // call fails below, so it writes no exit files.
294        let shutting_down = self.shutting_down.load(Ordering::SeqCst);
295        if shutting_down {
296            exit.drop_exit_paths();
297        }
298        // Under the lock, so that the child is registered before it can be
299        // forgotten. A task which never runs, like one spawned while the
300        // runtime shuts down, does not forget it, see `watch`.
301        let (grandchildren, forgotten) = (self.grandchildren.clone(), self.forgotten.clone());
302        let key = id.clone();
303        let exit_rx = reapable_grandchild.watch(exit, move || {
304            Self::forget_grandchild(&grandchildren, &key, seq);
305            forgotten.notify_waiters();
306        });
307        map.entry(id).or_default().push(reapable_grandchild);
308        // Ends the pending mark only once registered, so it is claimed all
309        // the time, see `is_claimed`.
310        drop(call);
311        drop(map);
312
313        // A call which was in progress while the shutdown killed the others.
314        if shutting_down {
315            Self::kill_late_grandchild(pid);
316            bail!("server is shutting down")
317        }
318        Ok(exit_rx)
319    }
320
321    /// Register and watch an exec which `create_child` started under the
322    /// `id` of its container, which gets killed at its `timeout`, see
323    /// [`ChildReaper::watch_grandchild`].
324    pub(crate) fn watch_exec(
325        &self,
326        id: Box<str>,
327        started: StartedChild,
328        timeout: Option<Instant>,
329        io: ContainerIO,
330    ) -> Result<WatchedExec> {
331        let StartedChild { pid, token, call } = started;
332        // Not reaped before the registration below, so the PID is still its
333        // own. Shared with the registration, one pidfd per exec.
334        let process = Arc::new(Process::open(pid));
335        let io = SharedContainerIO::new(io);
336        let child = Child {
337            id,
338            kind: ChildKind::Exec,
339            pid,
340            exit_paths: vec![],
341            oom_exit_paths: vec![],
342            timeout,
343            io: io.clone(),
344            token: token.clone(),
345            process: Some(process.clone()),
346        };
347        // The output stops once the process exited. Also if the registration
348        // fails, its watcher reports the exit.
349        let stop_output = io.attach().stop_output_on_exit(token.clone());
350        // The registration fails only while the server shuts down, which
351        // killed the process already.
352        let exit_rx = self.watch_grandchild(child, call)?;
353        Ok(WatchedExec {
354            io,
355            token,
356            process,
357            exit_rx,
358            stop_output,
359        })
360    }
361
362    /// Kill a grandchild registered after the shutdown killed the others,
363    /// unless it is gone already, and return whether it was signalled. The
364    /// sweep does not defer orphans while the server shuts down, so the
365    /// process may have exited and got reaped before its call claimed it.
366    /// Opening a pidfd fails with `ESRCH` then, and neither the PID nor the
367    /// group of the process gets signalled. A PID which got reused that fast
368    /// only gets signalled if it belongs to an unreaped child of the server,
369    /// see [`kill_grandchild`].
370    fn kill_late_grandchild(pid: Pid) -> bool {
371        if matches!(pidfd_open(pid), Err(Errno::ESRCH)) {
372            debug!(%pid, "Not killing grandchild, it got reaped already");
373            return false;
374        }
375        kill_grandchild(pid, Signal::SIGKILL);
376        true
377    }
378
379    /// Forget the child of the registration `seq` under `id`. Not by its PID,
380    /// which may be in use by a new child already.
381    fn forget_grandchild(
382        locked_grandchildren: &Arc<Mutex<HashMap<Box<str>, Vec<ReapableChild>>>>,
383        id: &str,
384        seq: u64,
385    ) {
386        let mut map = lock(locked_grandchildren);
387        let Some(children) = map.get_mut(id) else {
388            return;
389        };
390        if let Some(pos) = children.iter().position(|v| v.seq == seq) {
391            children.swap_remove(pos);
392        }
393        // Containers get a new ID on every restart, keeping the entries of
394        // the old ones would grow the map forever.
395        if children.is_empty() {
396            map.remove(id);
397        }
398    }
399
400    /// Forward SIGTERM to the containers like conmon, to their PIDs only,
401    /// and wait for their watchers until the `deadline`. Containers
402    /// registered meanwhile get neither, the shutdown kills them and what is
403    /// left afterwards. A container stays registered after its exit while
404    /// its watcher finishes the output, its PID may be in use by another
405    /// child by then, so it gets signalled through its pidfd, see
406    /// [`ReapableChild::process`].
407    pub(crate) async fn terminate_containers(&self, deadline: Instant) {
408        let containers: HashSet<_> = lock(&self.grandchildren)
409            .values()
410            .flatten()
411            .filter(|gc| gc.is_container())
412            .inspect(|gc| {
413                if let Err(e) = gc.process.signal(Signal::SIGTERM) {
414                    debug!(pid = %gc.pid, "Not signalling container, it is gone: {e}");
415                }
416            })
417            .map(|gc| gc.seq)
418            .collect();
419        // Nothing to tell for a server without containers.
420        if !containers.is_empty() {
421            info!("Sent SIGTERM to {} containers", containers.len());
422        }
423        let signalled = |gc: &ReapableChild| containers.contains(&gc.seq);
424        let left = self.wait_for_watchers(deadline, signalled).await;
425        if !left.is_empty() {
426            info!("Containers with PIDs {left:?} did not exit on SIGTERM");
427        }
428    }
429
430    /// Kill every grandchild and wait for their watchers and the runtime
431    /// calls in progress until the `deadline` of the shutdown, also for the
432    /// execs killed at their timeout which were not reaped yet. A process
433    /// which does not exit on SIGKILL, like one stuck in the kernel, must not
434    /// keep the server from stopping. It fails if they did not end in time.
435    pub(crate) async fn kill_grandchildren(&self, deadline: Instant) -> Result<()> {
436        let processes: Vec<_> = {
437            let map = lock(&self.grandchildren);
438            // Children registered after this snapshot get killed on their
439            // registration, see `watch_grandchild`.
440            self.shutting_down.store(true, Ordering::SeqCst);
441            map.values()
442                .flatten()
443                .map(|gc| gc.process.clone())
444                .collect()
445        };
446        if !processes.is_empty() {
447            info!("Killing {} containers and execs", processes.len());
448        }
449        // Signal every grandchild first, their watchers end concurrently, so
450        // that the shutdown takes as long as the slowest child instead of the
451        // sum of all of them. One which exited, whose watcher still finishes
452        // the output, only gets what remains of its group signalled, which
453        // may hold the pipes, see `terminate_containers`.
454        for process in processes {
455            let pid = process.pid();
456            let span = debug_span!("kill_grandchild", %pid);
457            let _enter = span.enter();
458            debug!("Killing single grandchild");
459            if let Err(e) = process.signal_group(Signal::SIGKILL) {
460                debug!("Not signalling grandchild, it is gone: {e}");
461            }
462        }
463        // An init of a PID namespace only exits once every process of the
464        // namespace is reaped, including zombies reparented to this process.
465        // The sweep keeps running while the watchers are awaited.
466        self.sweep.notify_one();
467
468        // Runtime calls in progress get to finish, the server would kill a
469        // runtime halfway otherwise. Their children got killed on
470        // registration. A call which hangs does not take the time of the
471        // watchers, which end meanwhile. It gets stopped early enough to
472        // clean up before the deadline.
473        let mut in_flight = self.in_flight.subscribe();
474        let stop_at = deadline
475            .checked_sub(Self::STOP_BUDGET)
476            .unwrap_or(deadline)
477            .min(Instant::now() + Self::IN_FLIGHT_WAIT);
478        let ended_in_time = time::timeout_at(stop_at, in_flight.wait_for(Calls::ended))
479            .await
480            .is_ok();
481        if !ended_in_time {
482            self.stop_calls.cancel();
483            self.kill_pending();
484        }
485        // Also the watchers of the children which the calls registered, and
486        // the calls which got stopped, which still clean up.
487        let (left, calls_ended) = tokio::join!(self.wait_for_watchers(deadline, |_| true), async {
488            ended_in_time
489                || time::timeout_at(deadline, in_flight.wait_for(Calls::ended))
490                    .await
491                    .is_ok()
492        },);
493
494        let calls = match (ended_in_time, calls_ended) {
495            (true, _) => None,
496            (false, true) => Some("runtime calls in progress got stopped"),
497            (false, false) => Some("runtime calls still in progress"),
498        };
499        match (left.is_empty(), calls) {
500            (true, None) => {
501                debug!("Done killing all grandchildren");
502                Ok(())
503            }
504            (true, Some(calls)) => bail!("{calls}"),
505            (false, None) => bail!("PIDs {left:?} did not exit"),
506            (false, Some(calls)) => bail!("PIDs {left:?} did not exit, {calls}"),
507        }
508    }
509
510    /// How long the shutdown waits for runtime calls in progress to end on
511    /// their own, within its deadline. Those which take longer get stopped,
512    /// see [`ChildReaper::kill_pending`], and get the rest of the deadline
513    /// to end, at least the [`ChildReaper::STOP_BUDGET`].
514    const IN_FLIGHT_WAIT: Duration = Duration::from_secs(5);
515
516    /// How long before the deadline of the shutdown the runtime calls in
517    /// progress get stopped at the latest. A stopped call kills its runtime
518    /// right away, waits for its exit and its output only briefly, and kills
519    /// the process the runtime left, see `stop_runtime`, which this covers
520    /// with some time to spare for the scheduling.
521    pub(crate) const STOP_BUDGET: Duration = Duration::from_secs(1);
522
523    /// Kill the processes which the runtime calls that did not end in time
524    /// started but did not register yet, like a created container whose
525    /// call waits for the output of its runtime. The server stops without
526    /// the calls, a process they leave would run without a watcher.
527    ///
528    /// The calls got stopped before, see [`ChildReaper::stop_calls`]. Their
529    /// runtimes are not signalled here: tokio reaps them without the
530    /// `REAP_LOCK` of [`Process`], so the PID of one may belong to another
531    /// process by now. A call kills its runtime itself while it still holds
532    /// it unreaped, see `stop_runtime`. The processes which the calls
533    /// claimed get reaped only by the orphan sweep and by their watchers,
534    /// under that lock, see [`kill_grandchild`]. Under the lock of the
535    /// grandchildren no call registers its process meanwhile, whose watcher
536    /// could reap it then, and under the one of the calls no call ends,
537    /// after which the sweep could reap it. A call which claims its process
538    /// later kills it itself, see [`InFlight::claim`].
539    fn kill_pending(&self) {
540        let _map = lock(&self.grandchildren);
541        let calls = self.in_flight.borrow();
542        for pid in calls.in_progress.values().filter_map(|call| call.process) {
543            debug!(%pid, "Killing process of a runtime call in progress");
544            kill_grandchild(pid, Signal::SIGKILL);
545        }
546    }
547
548    /// Wait until the watchers of all grandchildren which match the `filter`
549    /// ended, every watcher forgets its child at its end. Return the PIDs of
550    /// the children left at the deadline.
551    async fn wait_for_watchers(
552        &self,
553        deadline: Instant,
554        filter: impl Fn(&ReapableChild) -> bool,
555    ) -> Vec<i32> {
556        loop {
557            // Enabled before the check, so that no end gets missed.
558            let mut forgotten = pin!(self.forgotten.notified());
559            forgotten.as_mut().enable();
560            let mut left: Vec<_> = lock(&self.grandchildren)
561                .values()
562                .flatten()
563                .filter(|gc| filter(gc))
564                .map(|gc| gc.pid.as_raw())
565                .collect();
566            if left.is_empty() || Instant::now() >= deadline {
567                left.sort_unstable();
568                return left;
569            }
570            debug!("Waiting for {} watchers to end", left.len());
571            // Checked again at the deadline.
572            let _ = time::timeout_at(deadline, forgotten).await;
573        }
574    }
575}
576
577/// Signal a grandchild and its process group, unless it got reaped already,
578/// see [`Process::signal_group`]. The grandchildren got reparented to this
579/// subreaper, so the PID refers to a child.
580fn kill_grandchild(pid: Pid, s: Signal) {
581    if let Err(e) = Process::child(pid).signal_group(s) {
582        debug!(%pid, "Not signalling grandchild, it is gone: {e}");
583    }
584}
585
586/// An exec which got registered, see [`ChildReaper::watch_exec`].
587pub(crate) struct WatchedExec {
588    pub(crate) io: SharedContainerIO,
589
590    /// Cancelled once the process exited.
591    pub(crate) token: CancellationToken,
592
593    /// The process, which its watcher may reap before the token got
594    /// cancelled, so it does not get signalled by its PID.
595    pub(crate) process: Arc<Process>,
596    pub(crate) exit_rx: Receiver<ExitChannelData>,
597
598    /// Stops the output once dropped by its consumer after the exit.
599    pub(crate) stop_output: StopOutput,
600}
601
602/// A registered child, until its watcher ended. What only the watcher needs
603/// moves into it, see [`ExitHandling`](watcher::ExitHandling).
604#[derive(Clone, Debug)]
605pub(crate) struct ReapableChild {
606    /// Tells the registrations apart, unlike the PID, which gets reused.
607    seq: u64,
608    pid: Pid,
609
610    /// Opened at the registration, before the watcher could reap the child.
611    /// The watcher reaps it before it cancels the token, or after that for
612    /// one killed at its timeout. Once it got reaped, the PID may belong to
613    /// another process, which the signals through the pidfd do not reach.
614    /// Kernels without pidfds (before 5.3) only get the check of the PID,
615    /// see [`Process::open`].
616    process: Arc<Process>,
617
618    /// Set once the watcher reaped the child. It stays registered while the
619    /// watcher finishes its output, its PID may belong to another child by
620    /// then, which the registration must not claim, see
621    /// [`ChildReaper::is_claimed`].
622    reaped: Arc<AtomicBool>,
623
624    /// The stdio of a container, which container level operations use, see
625    /// [`ChildReaper::get`]. None for an exec, only its consumer holds its
626    /// stdio, which also tells the kinds apart. One killed at its timeout stays registered until it got
627    /// reaped, which may take until the server stops, but its pipes or its
628    /// terminal get released with the consumer.
629    io: Option<SharedContainerIO>,
630    token: CancellationToken,
631}
632
633impl ReapableChild {
634    /// The PID of the child.
635    pub(crate) fn pid(&self) -> Pid {
636        self.pid
637    }
638
639    /// The PID of the child while its watcher still waits for it, see
640    /// [`Self::reaped`].
641    fn claimed_pid(&self) -> Option<Pid> {
642        (!self.reaped.load(Ordering::Acquire)).then_some(self.pid)
643    }
644
645    /// Whether the child is a container, not an exec, see [`Self::io`].
646    fn is_container(&self) -> bool {
647        self.io.is_some()
648    }
649
650    /// The container, none for an exec.
651    pub(crate) fn container(&self) -> Option<RegisteredContainer> {
652        Some(RegisteredContainer {
653            io: self.io.clone()?,
654            token: self.token.clone(),
655        })
656    }
657}
658
659/// A registered container, see [`ChildReaper::get`].
660#[derive(Clone, Debug)]
661pub(crate) struct RegisteredContainer {
662    io: SharedContainerIO,
663    token: CancellationToken,
664}
665
666impl RegisteredContainer {
667    /// The stdio of the container.
668    pub(crate) fn io(&self) -> &SharedContainerIO {
669        &self.io
670    }
671
672    /// Cancelled once the container exited.
673    pub(crate) fn token(&self) -> &CancellationToken {
674        &self.token
675    }
676}
677
678// A stopped call ends within its budget.
679const _: () = assert!(
680    ChildReaper::STOP_BUDGET.as_nanos()
681        > ChildReaper::STOPPED_EXIT_WAIT.as_nanos() + ChildReaper::STOPPED_OUTPUT_WAIT.as_nanos()
682);
683
684/// The start of the error of a request for a container the server does not
685/// know, clients match it, see `ErrContainerNotFound` of the Go client. The
686/// message of older servers is "child not available".
687const CONTAINER_NOT_FOUND: &str = "container not found";
688
689/// The exit code of an exec which could not be started, for example because
690/// the runtime failed.
691pub(crate) const EXIT_CODE_NOT_STARTED: i32 = -2;
692
693/// The exit code of a process which did not exit before its timeout. Clients
694/// take -3 for a timeout, no other failure may report it, except for
695/// [`EXIT_CODE_WAIT_FAILED`](crate::process::EXIT_CODE_WAIT_FAILED), which
696/// always did.
697pub(crate) const EXIT_CODE_TIMED_OUT: i32 = -3;
698
699/// The exit of a child, which its waiters get.
700#[derive(Clone, Debug)]
701pub(crate) struct ExitChannelData {
702    pub(crate) exit_code: i32,
703    pub(crate) timed_out: bool,
704}
705
706#[cfg(test)]
707mod tests;