]> git.lizzy.rs Git - rust.git/blob - src/bootstrap/job.rs
Auto merge of #107843 - bjorn3:sync_cg_clif-2023-02-09, r=bjorn3
[rust.git] / src / bootstrap / job.rs
1 //! Job management on Windows for bootstrapping
2 //!
3 //! Most of the time when you're running a build system (e.g., make) you expect
4 //! Ctrl-C or abnormal termination to actually terminate the entire tree of
5 //! process in play, not just the one at the top. This currently works "by
6 //! default" on Unix platforms because Ctrl-C actually sends a signal to the
7 //! *process group* rather than the parent process, so everything will get torn
8 //! down. On Windows, however, this does not happen and Ctrl-C just kills the
9 //! parent process.
10 //!
11 //! To achieve the same semantics on Windows we use Job Objects to ensure that
12 //! all processes die at the same time. Job objects have a mode of operation
13 //! where when all handles to the object are closed it causes all child
14 //! processes associated with the object to be terminated immediately.
15 //! Conveniently whenever a process in the job object spawns a new process the
16 //! child will be associated with the job object as well. This means if we add
17 //! ourselves to the job object we create then everything will get torn down!
18 //!
19 //! Unfortunately most of the time the build system is actually called from a
20 //! python wrapper (which manages things like building the build system) so this
21 //! all doesn't quite cut it so far. To go the last mile we duplicate the job
22 //! object handle into our parent process (a python process probably) and then
23 //! close our own handle. This means that the only handle to the job object
24 //! resides in the parent python process, so when python dies the whole build
25 //! system dies (as one would probably expect!).
26 //!
27 //! Note that this module has a #[cfg(windows)] above it as none of this logic
28 //! is required on Unix.
29
30 #![allow(nonstandard_style, dead_code)]
31
32 use crate::Build;
33 use std::env;
34 use std::io;
35 use std::mem;
36 use std::ptr;
37
38 use winapi::shared::minwindef::{DWORD, FALSE, LPVOID};
39 use winapi::um::errhandlingapi::SetErrorMode;
40 use winapi::um::handleapi::{CloseHandle, DuplicateHandle};
41 use winapi::um::jobapi2::{AssignProcessToJobObject, CreateJobObjectW, SetInformationJobObject};
42 use winapi::um::processthreadsapi::{GetCurrentProcess, OpenProcess};
43 use winapi::um::winbase::{BELOW_NORMAL_PRIORITY_CLASS, SEM_NOGPFAULTERRORBOX};
44 use winapi::um::winnt::{
45     JobObjectExtendedLimitInformation, DUPLICATE_SAME_ACCESS, JOBOBJECT_EXTENDED_LIMIT_INFORMATION,
46     JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, JOB_OBJECT_LIMIT_PRIORITY_CLASS, PROCESS_DUP_HANDLE,
47 };
48
49 pub unsafe fn setup(build: &mut Build) {
50     // Enable the Windows Error Reporting dialog which msys disables,
51     // so we can JIT debug rustc
52     let mode = SetErrorMode(0);
53     SetErrorMode(mode & !SEM_NOGPFAULTERRORBOX);
54
55     // Create a new job object for us to use
56     let job = CreateJobObjectW(ptr::null_mut(), ptr::null());
57     assert!(!job.is_null(), "{}", io::Error::last_os_error());
58
59     // Indicate that when all handles to the job object are gone that all
60     // process in the object should be killed. Note that this includes our
61     // entire process tree by default because we've added ourselves and our
62     // children will reside in the job by default.
63     let mut info = mem::zeroed::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>();
64     info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
65     if build.config.low_priority {
66         info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_PRIORITY_CLASS;
67         info.BasicLimitInformation.PriorityClass = BELOW_NORMAL_PRIORITY_CLASS;
68     }
69     let r = SetInformationJobObject(
70         job,
71         JobObjectExtendedLimitInformation,
72         &mut info as *mut _ as LPVOID,
73         mem::size_of_val(&info) as DWORD,
74     );
75     assert!(r != 0, "{}", io::Error::last_os_error());
76
77     // Assign our process to this job object. Note that if this fails, one very
78     // likely reason is that we are ourselves already in a job object! This can
79     // happen on the build bots that we've got for Windows, or if just anyone
80     // else is instrumenting the build. In this case we just bail out
81     // immediately and assume that they take care of it.
82     //
83     // Also note that nested jobs (why this might fail) are supported in recent
84     // versions of Windows, but the version of Windows that our bots are running
85     // at least don't support nested job objects.
86     let r = AssignProcessToJobObject(job, GetCurrentProcess());
87     if r == 0 {
88         CloseHandle(job);
89         return;
90     }
91
92     // If we've got a parent process (e.g., the python script that called us)
93     // then move ownership of this job object up to them. That way if the python
94     // script is killed (e.g., via ctrl-c) then we'll all be torn down.
95     //
96     // If we don't have a parent (e.g., this was run directly) then we
97     // intentionally leak the job object handle. When our process exits
98     // (normally or abnormally) it will close the handle implicitly, causing all
99     // processes in the job to be cleaned up.
100     let pid = match env::var("BOOTSTRAP_PARENT_ID") {
101         Ok(s) => s,
102         Err(..) => return,
103     };
104
105     let parent = OpenProcess(PROCESS_DUP_HANDLE, FALSE, pid.parse().unwrap());
106
107     // If we get a null parent pointer here, it is possible that either
108     // we have got an invalid pid or the parent process has been closed.
109     // Since the first case rarely happens
110     // (only when wrongly setting the environmental variable),
111     // so it might be better to improve the experience of the second case
112     // when users have interrupted the parent process and we don't finish
113     // duplicating the handle yet.
114     // We just need close the job object if that occurs.
115     if parent.is_null() {
116         CloseHandle(job);
117         return;
118     }
119
120     let mut parent_handle = ptr::null_mut();
121     let r = DuplicateHandle(
122         GetCurrentProcess(),
123         job,
124         parent,
125         &mut parent_handle,
126         0,
127         FALSE,
128         DUPLICATE_SAME_ACCESS,
129     );
130
131     // If this failed, well at least we tried! An example of DuplicateHandle
132     // failing in the past has been when the wrong python2 package spawned this
133     // build system (e.g., the `python2` package in MSYS instead of
134     // `mingw-w64-x86_64-python2`. Not sure why it failed, but the "failure
135     // mode" here is that we only clean everything up when the build system
136     // dies, not when the python parent does, so not too bad.
137     if r != 0 {
138         CloseHandle(job);
139     }
140 }