Skip to content

Commit cfdb25d

Browse files
authored
Merge pull request #79 from cgwalters/lifecycle-bind
cmdext: Add prctl wrapper
2 parents 63c131d + 19ce424 commit cfdb25d

File tree

2 files changed

+38
-0
lines changed

2 files changed

+38
-0
lines changed

src/cmdext.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,19 @@ pub trait CapStdExtCommandExt {
2424

2525
/// Use the given directory as the current working directory for the process.
2626
fn cwd_dir(&mut self, dir: Dir) -> &mut Self;
27+
28+
/// On Linux, arrange for [`SIGTERM`] to be delivered to the child if the
29+
/// parent *thread* exits. This helps avoid leaking child processes if
30+
/// the parent crashes for example.
31+
///
32+
/// # IMPORTANT
33+
///
34+
/// Due to the semantics of <https://man7.org/linux/man-pages/man2/prctl.2.html> this
35+
/// will cause the child to exit when the parent *thread* (not process) exits. In
36+
/// particular this can become problematic when used with e.g. a threadpool such
37+
/// as Tokio's <https://kobzol.github.io/rust/2025/02/23/tokio-plus-prctl-equals-nasty-bug.html>.
38+
#[cfg(any(target_os = "linux", target_os = "android"))]
39+
fn lifecycle_bind_to_parent_thread(&mut self) -> &mut Self;
2740
}
2841

2942
#[allow(unsafe_code)]
@@ -58,6 +71,20 @@ impl CapStdExtCommandExt for std::process::Command {
5871
}
5972
self
6073
}
74+
75+
#[cfg(any(target_os = "linux", target_os = "android"))]
76+
fn lifecycle_bind_to_parent_thread(&mut self) -> &mut Self {
77+
// SAFETY: This API is safe to call in a forked child.
78+
unsafe {
79+
self.pre_exec(|| {
80+
rustix::process::set_parent_process_death_signal(Some(
81+
rustix::process::Signal::TERM,
82+
))
83+
.map_err(Into::into)
84+
});
85+
}
86+
self
87+
}
6188
}
6289

6390
#[cfg(test)]

tests/it/main.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -791,3 +791,14 @@ fn test_big_xattr() -> Result<()> {
791791

792792
Ok(())
793793
}
794+
795+
#[test]
796+
#[cfg(any(target_os = "linux", target_os = "android"))]
797+
fn test_lifecycle_bind_to_parent_thread() -> Result<()> {
798+
let status = Command::new("true")
799+
.lifecycle_bind_to_parent_thread()
800+
.status()?;
801+
assert!(status.success());
802+
803+
Ok(())
804+
}

0 commit comments

Comments
 (0)