diff --git a/src/cmdext.rs b/src/cmdext.rs index 5fe4ad3..fb40603 100644 --- a/src/cmdext.rs +++ b/src/cmdext.rs @@ -24,6 +24,19 @@ pub trait CapStdExtCommandExt { /// Use the given directory as the current working directory for the process. fn cwd_dir(&mut self, dir: Dir) -> &mut Self; + + /// On Linux, arrange for [`SIGTERM`] to be delivered to the child if the + /// parent *thread* exits. This helps avoid leaking child processes if + /// the parent crashes for example. + /// + /// # IMPORTANT + /// + /// Due to the semantics of this + /// will cause the child to exit when the parent *thread* (not process) exits. In + /// particular this can become problematic when used with e.g. a threadpool such + /// as Tokio's . + #[cfg(any(target_os = "linux", target_os = "android"))] + fn lifecycle_bind_to_parent_thread(&mut self) -> &mut Self; } #[allow(unsafe_code)] @@ -58,6 +71,20 @@ impl CapStdExtCommandExt for std::process::Command { } self } + + #[cfg(any(target_os = "linux", target_os = "android"))] + fn lifecycle_bind_to_parent_thread(&mut self) -> &mut Self { + // SAFETY: This API is safe to call in a forked child. + unsafe { + self.pre_exec(|| { + rustix::process::set_parent_process_death_signal(Some( + rustix::process::Signal::TERM, + )) + .map_err(Into::into) + }); + } + self + } } #[cfg(test)] diff --git a/tests/it/main.rs b/tests/it/main.rs index 3de4152..6f54a85 100644 --- a/tests/it/main.rs +++ b/tests/it/main.rs @@ -675,3 +675,14 @@ fn test_big_xattr() -> Result<()> { Ok(()) } + +#[test] +#[cfg(any(target_os = "linux", target_os = "android"))] +fn test_lifecycle_bind_to_parent_thread() -> Result<()> { + let status = Command::new("true") + .lifecycle_bind_to_parent_thread() + .status()?; + assert!(status.success()); + + Ok(()) +}