1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
// SPDX-License-Identifier: Apache-2.0
use libdd_common::timeout::TimeoutManager;
use libdd_common::unix_utils::{reap_child_non_blocking, wait_for_pollhup};
use nix::errno::Errno;
use nix::unistd::Pid;
use std::os::unix::io::RawFd;
pub(crate) struct ProcessHandle {
pub uds_fd: RawFd,
pub pid: Option<i32>,
}
impl ProcessHandle {
pub fn new(uds_fd: RawFd, pid: Option<i32>) -> Self {
Self { uds_fd, pid }
}
pub fn finish(&self, timeout_manager: &TimeoutManager) {
let result = wait_for_pollhup(self.uds_fd, timeout_manager);
debug_assert_eq!(result, Ok(true), "wait_for_pollhup failed: {result:?}");
if let Some(pid) = self.pid {
// If we have less than the minimum amount of time, give ourselves a few scheduler
// slices worth of headroom to help guarantee that we don't leak a zombie process.
let kill_result = unsafe { libc::kill(pid, libc::SIGKILL) };
if kill_result != 0 {
let errno = Errno::last_raw();
if errno == libc::ESRCH {
// Child has already exited, which is fine; no action needed
} else {
eprintln!("Warning: kill({pid}, SIGKILL) failed with errno {errno}");
debug_assert_eq!(
errno,
libc::ESRCH,
"kill failed with result: {kill_result}, errno: {errno}"
);
}
}
// `self` is actually a handle to a child process and `self.pid` is the child process's
// pid.
let child_pid = Pid::from_raw(pid);
let result = reap_child_non_blocking(child_pid, timeout_manager);
// Ok(true): child was successfully reaped
// Ok(false): child was already reaped (ECHILD) -> no zombie risk
// This can happen because we defer SIGCHLD during crash handling
debug_assert!(
matches!(result, Ok(true) | Ok(false)),
"reap_child_non_blocking failed: {result:?}"
);
}
}
}