cargo/util/
job.rs

1//! Job management (mostly for windows)
2//!
3//! Most of the time when you're running cargo you expect Ctrl-C to actually
4//! terminate the entire tree of processes in play, not just the one at the top
5//! (cargo). This currently works "by default" on Unix platforms because Ctrl-C
6//! actually sends a signal to the *process group* rather than the parent
7//! process, so everything will get torn down. On Windows, however, this does
8//! not happen and Ctrl-C just kills cargo.
9//!
10//! To achieve the same semantics on Windows we use Job Objects to ensure that
11//! all processes die at the same time. Job objects have a mode of operation
12//! where when all handles to the object are closed it causes all child
13//! processes associated with the object to be terminated immediately.
14//! Conveniently whenever a process in the job object spawns a new process the
15//! child will be associated with the job object as well. This means if we add
16//! ourselves to the job object we create then everything will get torn down!
17
18pub use self::imp::Setup;
19
20pub fn setup() -> Option<Setup> {
21    unsafe { imp::setup() }
22}
23
24#[cfg(unix)]
25mod imp {
26    use std::env;
27
28    pub type Setup = ();
29
30    pub unsafe fn setup() -> Option<()> {
31        // There's a test case for the behavior of
32        // when-cargo-is-killed-subprocesses-are-also-killed, but that requires
33        // one cargo spawned to become its own session leader, so we do that
34        // here.
35        #[expect(
36            clippy::disallowed_methods,
37            reason = "testing only, no reason for config support"
38        )]
39        if env::var("__CARGO_TEST_SETSID_PLEASE_DONT_USE_ELSEWHERE").is_ok() {
40            // SAFETY: I'm unaware of any safety requirements for this function.
41            unsafe {
42                libc::setsid();
43            }
44        }
45        Some(())
46    }
47}
48
49#[cfg(windows)]
50mod imp {
51    use std::io;
52    use std::mem;
53    use std::ptr;
54    use std::ptr::addr_of;
55
56    use tracing::info;
57
58    use windows_sys::Win32::Foundation::CloseHandle;
59    use windows_sys::Win32::Foundation::HANDLE;
60    use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;
61    use windows_sys::Win32::System::JobObjects::AssignProcessToJobObject;
62    use windows_sys::Win32::System::JobObjects::CreateJobObjectW;
63    use windows_sys::Win32::System::JobObjects::JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
64    use windows_sys::Win32::System::JobObjects::JOBOBJECT_EXTENDED_LIMIT_INFORMATION;
65    use windows_sys::Win32::System::JobObjects::JobObjectExtendedLimitInformation;
66    use windows_sys::Win32::System::JobObjects::SetInformationJobObject;
67    use windows_sys::Win32::System::Threading::GetCurrentProcess;
68
69    pub struct Setup {
70        job: Handle,
71    }
72
73    pub struct Handle {
74        inner: HANDLE,
75    }
76
77    fn last_err() -> io::Error {
78        io::Error::last_os_error()
79    }
80
81    pub unsafe fn setup() -> Option<Setup> {
82        // Creates a new job object for us to use and then adds ourselves to it.
83        // Note that all errors are basically ignored in this function,
84        // intentionally. Job objects are "relatively new" in Windows,
85        // particularly the ability to support nested job objects. Older
86        // Windows installs don't support this ability. We probably don't want
87        // to force Cargo to abort in this situation or force others to *not*
88        // use job objects, so we instead just ignore errors and assume that
89        // we're otherwise part of someone else's job object in this case.
90
91        let job = unsafe { CreateJobObjectW(ptr::null_mut(), ptr::null()) };
92        if job == INVALID_HANDLE_VALUE {
93            return None;
94        }
95        let job = Handle { inner: job };
96
97        // Indicate that when all handles to the job object are gone that all
98        // process in the object should be killed. Note that this includes our
99        // entire process tree by default because we've added ourselves and
100        // our children will reside in the job once we spawn a process.
101        let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION;
102        info = unsafe { mem::zeroed() };
103        info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
104        let r = unsafe {
105            SetInformationJobObject(
106                job.inner,
107                JobObjectExtendedLimitInformation,
108                addr_of!(info) as *const _,
109                mem::size_of_val(&info) as u32,
110            )
111        };
112        if r == 0 {
113            return None;
114        }
115
116        // Assign our process to this job object, meaning that our children will
117        // now live or die based on our existence.
118        let me = unsafe { GetCurrentProcess() };
119        let r = unsafe { AssignProcessToJobObject(job.inner, me) };
120        if r == 0 {
121            return None;
122        }
123
124        Some(Setup { job })
125    }
126
127    impl Drop for Setup {
128        fn drop(&mut self) {
129            // On normal exits (not ctrl-c), we don't want to kill any child
130            // processes. The destructor here configures our job object to
131            // **not** kill everything on close, then closes the job object.
132            unsafe {
133                let info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION;
134                info = mem::zeroed();
135                let r = SetInformationJobObject(
136                    self.job.inner,
137                    JobObjectExtendedLimitInformation,
138                    addr_of!(info) as *const _,
139                    mem::size_of_val(&info) as u32,
140                );
141                if r == 0 {
142                    info!("failed to configure job object to defaults: {}", last_err());
143                }
144            }
145        }
146    }
147
148    impl Drop for Handle {
149        fn drop(&mut self) {
150            unsafe {
151                CloseHandle(self.inner);
152            }
153        }
154    }
155}