]> git.lizzy.rs Git - rust.git/blob - src/bootstrap/job.rs
Rollup merge of #67531 - RalfJung:tame-promotion, r=nikomatsakis
[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 type HANDLE = *mut u8;
39 type BOOL = i32;
40 type DWORD = u32;
41 type LPHANDLE = *mut HANDLE;
42 type LPVOID = *mut u8;
43 type JOBOBJECTINFOCLASS = i32;
44 type SIZE_T = usize;
45 type LARGE_INTEGER = i64;
46 type UINT = u32;
47 type ULONG_PTR = usize;
48 type ULONGLONG = u64;
49
50 const FALSE: BOOL = 0;
51 const DUPLICATE_SAME_ACCESS: DWORD = 0x2;
52 const PROCESS_DUP_HANDLE: DWORD = 0x40;
53 const JobObjectExtendedLimitInformation: JOBOBJECTINFOCLASS = 9;
54 const JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE: DWORD = 0x2000;
55 const JOB_OBJECT_LIMIT_PRIORITY_CLASS: DWORD = 0x00000020;
56 const SEM_FAILCRITICALERRORS: UINT = 0x0001;
57 const SEM_NOGPFAULTERRORBOX: UINT = 0x0002;
58 const BELOW_NORMAL_PRIORITY_CLASS: DWORD = 0x00004000;
59
60 extern "system" {
61     fn CreateJobObjectW(lpJobAttributes: *mut u8, lpName: *const u8) -> HANDLE;
62     fn CloseHandle(hObject: HANDLE) -> BOOL;
63     fn GetCurrentProcess() -> HANDLE;
64     fn OpenProcess(dwDesiredAccess: DWORD, bInheritHandle: BOOL, dwProcessId: DWORD) -> HANDLE;
65     fn DuplicateHandle(
66         hSourceProcessHandle: HANDLE,
67         hSourceHandle: HANDLE,
68         hTargetProcessHandle: HANDLE,
69         lpTargetHandle: LPHANDLE,
70         dwDesiredAccess: DWORD,
71         bInheritHandle: BOOL,
72         dwOptions: DWORD,
73     ) -> BOOL;
74     fn AssignProcessToJobObject(hJob: HANDLE, hProcess: HANDLE) -> BOOL;
75     fn SetInformationJobObject(
76         hJob: HANDLE,
77         JobObjectInformationClass: JOBOBJECTINFOCLASS,
78         lpJobObjectInformation: LPVOID,
79         cbJobObjectInformationLength: DWORD,
80     ) -> BOOL;
81     fn SetErrorMode(mode: UINT) -> UINT;
82 }
83
84 #[repr(C)]
85 struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION {
86     BasicLimitInformation: JOBOBJECT_BASIC_LIMIT_INFORMATION,
87     IoInfo: IO_COUNTERS,
88     ProcessMemoryLimit: SIZE_T,
89     JobMemoryLimit: SIZE_T,
90     PeakProcessMemoryUsed: SIZE_T,
91     PeakJobMemoryUsed: SIZE_T,
92 }
93
94 #[repr(C)]
95 struct IO_COUNTERS {
96     ReadOperationCount: ULONGLONG,
97     WriteOperationCount: ULONGLONG,
98     OtherOperationCount: ULONGLONG,
99     ReadTransferCount: ULONGLONG,
100     WriteTransferCount: ULONGLONG,
101     OtherTransferCount: ULONGLONG,
102 }
103
104 #[repr(C)]
105 struct JOBOBJECT_BASIC_LIMIT_INFORMATION {
106     PerProcessUserTimeLimit: LARGE_INTEGER,
107     PerJobUserTimeLimit: LARGE_INTEGER,
108     LimitFlags: DWORD,
109     MinimumWorkingsetSize: SIZE_T,
110     MaximumWorkingsetSize: SIZE_T,
111     ActiveProcessLimit: DWORD,
112     Affinity: ULONG_PTR,
113     PriorityClass: DWORD,
114     SchedulingClass: DWORD,
115 }
116
117 pub unsafe fn setup(build: &mut Build) {
118     // Enable the Windows Error Reporting dialog which msys disables,
119     // so we can JIT debug rustc
120     let mode = SetErrorMode(0);
121     SetErrorMode(mode & !SEM_NOGPFAULTERRORBOX);
122
123     // Create a new job object for us to use
124     let job = CreateJobObjectW(ptr::null_mut(), ptr::null());
125     assert!(!job.is_null(), "{}", io::Error::last_os_error());
126
127     // Indicate that when all handles to the job object are gone that all
128     // process in the object should be killed. Note that this includes our
129     // entire process tree by default because we've added ourselves and our
130     // children will reside in the job by default.
131     let mut info = mem::zeroed::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>();
132     info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
133     if build.config.low_priority {
134         info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_PRIORITY_CLASS;
135         info.BasicLimitInformation.PriorityClass = BELOW_NORMAL_PRIORITY_CLASS;
136     }
137     let r = SetInformationJobObject(
138         job,
139         JobObjectExtendedLimitInformation,
140         &mut info as *mut _ as LPVOID,
141         mem::size_of_val(&info) as DWORD,
142     );
143     assert!(r != 0, "{}", io::Error::last_os_error());
144
145     // Assign our process to this job object. Note that if this fails, one very
146     // likely reason is that we are ourselves already in a job object! This can
147     // happen on the build bots that we've got for Windows, or if just anyone
148     // else is instrumenting the build. In this case we just bail out
149     // immediately and assume that they take care of it.
150     //
151     // Also note that nested jobs (why this might fail) are supported in recent
152     // versions of Windows, but the version of Windows that our bots are running
153     // at least don't support nested job objects.
154     let r = AssignProcessToJobObject(job, GetCurrentProcess());
155     if r == 0 {
156         CloseHandle(job);
157         return;
158     }
159
160     // If we've got a parent process (e.g., the python script that called us)
161     // then move ownership of this job object up to them. That way if the python
162     // script is killed (e.g., via ctrl-c) then we'll all be torn down.
163     //
164     // If we don't have a parent (e.g., this was run directly) then we
165     // intentionally leak the job object handle. When our process exits
166     // (normally or abnormally) it will close the handle implicitly, causing all
167     // processes in the job to be cleaned up.
168     let pid = match env::var("BOOTSTRAP_PARENT_ID") {
169         Ok(s) => s,
170         Err(..) => return,
171     };
172
173     let parent = OpenProcess(PROCESS_DUP_HANDLE, FALSE, pid.parse().unwrap());
174     assert!(!parent.is_null(), "{}", io::Error::last_os_error());
175     let mut parent_handle = ptr::null_mut();
176     let r = DuplicateHandle(
177         GetCurrentProcess(),
178         job,
179         parent,
180         &mut parent_handle,
181         0,
182         FALSE,
183         DUPLICATE_SAME_ACCESS,
184     );
185
186     // If this failed, well at least we tried! An example of DuplicateHandle
187     // failing in the past has been when the wrong python2 package spawned this
188     // build system (e.g., the `python2` package in MSYS instead of
189     // `mingw-w64-x86_64-python2`. Not sure why it failed, but the "failure
190     // mode" here is that we only clean everything up when the build system
191     // dies, not when the python parent does, so not too bad.
192     if r != 0 {
193         CloseHandle(job);
194     }
195 }