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;