]> git.lizzy.rs Git - rust.git/blob - src/bootstrap/job.rs
Rollup merge of #44533 - nrc:rustfmt-submod, r=alexcrichton
[rust.git] / src / bootstrap / job.rs
1 // Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 //! Job management on Windows for bootstrapping
12 //!
13 //! Most of the time when you're running a build system (e.g. make) you expect
14 //! Ctrl-C or abnormal termination to actually terminate the entire tree of
15 //! process in play, not just the one at the top. This currently works "by
16 //! default" on Unix platforms because Ctrl-C actually sends a signal to the
17 //! *process group* rather than the parent process, so everything will get torn
18 //! down. On Windows, however, this does not happen and Ctrl-C just kills the
19 //! parent process.
20 //!
21 //! To achieve the same semantics on Windows we use Job Objects to ensure that
22 //! all processes die at the same time. Job objects have a mode of operation
23 //! where when all handles to the object are closed it causes all child
24 //! processes associated with the object to be terminated immediately.
25 //! Conveniently whenever a process in the job object spawns a new process the
26 //! child will be associated with the job object as well. This means if we add
27 //! ourselves to the job object we create then everything will get torn down!
28 //!
29 //! Unfortunately most of the time the build system is actually called from a
30 //! python wrapper (which manages things like building the build system) so this
31 //! all doesn't quite cut it so far. To go the last mile we duplicate the job
32 //! object handle into our parent process (a python process probably) and then
33 //! close our own handle. This means that the only handle to the job object
34 //! resides in the parent python process, so when python dies the whole build
35 //! system dies (as one would probably expect!).
36 //!
37 //! Note that this module has a #[cfg(windows)] above it as none of this logic
38 //! is required on Unix.
39
40 #![allow(bad_style, dead_code)]
41
42 use std::env;
43 use std::io;
44 use std::mem;
45 use Build;
46
47 type HANDLE = *mut u8;
48 type BOOL = i32;
49 type DWORD = u32;
50 type LPHANDLE = *mut HANDLE;
51 type LPVOID = *mut u8;
52 type JOBOBJECTINFOCLASS = i32;
53 type SIZE_T = usize;
54 type LARGE_INTEGER = i64;
55 type UINT = u32;
56 type ULONG_PTR = usize;
57 type ULONGLONG = u64;
58
59 const FALSE: BOOL = 0;
60 const DUPLICATE_SAME_ACCESS: DWORD = 0x2;
61 const PROCESS_DUP_HANDLE: DWORD = 0x40;
62 const JobObjectExtendedLimitInformation: JOBOBJECTINFOCLASS = 9;
63 const JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE: DWORD = 0x2000;
64 const JOB_OBJECT_LIMIT_PRIORITY_CLASS: DWORD = 0x00000020;
65 const SEM_FAILCRITICALERRORS: UINT = 0x0001;
66 const SEM_NOGPFAULTERRORBOX: UINT = 0x0002;
67 const BELOW_NORMAL_PRIORITY_CLASS: DWORD = 0x00004000;
68
69 extern "system" {
70     fn CreateJobObjectW(lpJobAttributes: *mut u8, lpName: *const u8) -> HANDLE;
71     fn CloseHandle(hObject: HANDLE) -> BOOL;
72     fn GetCurrentProcess() -> HANDLE;
73     fn OpenProcess(dwDesiredAccess: DWORD,
74                    bInheritHandle: BOOL,
75                    dwProcessId: DWORD) -> HANDLE;
76     fn DuplicateHandle(hSourceProcessHandle: HANDLE,
77                        hSourceHandle: HANDLE,
78                        hTargetProcessHandle: HANDLE,
79                        lpTargetHandle: LPHANDLE,
80                        dwDesiredAccess: DWORD,
81                        bInheritHandle: BOOL,
82                        dwOptions: DWORD) -> BOOL;
83     fn AssignProcessToJobObject(hJob: HANDLE, hProcess: HANDLE) -> BOOL;
84     fn SetInformationJobObject(hJob: HANDLE,
85                                JobObjectInformationClass: JOBOBJECTINFOCLASS,
86                                lpJobObjectInformation: LPVOID,
87                                cbJobObjectInformationLength: DWORD) -> BOOL;
88     fn SetErrorMode(mode: UINT) -> UINT;
89 }
90
91 #[repr(C)]
92 struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION {
93     BasicLimitInformation: JOBOBJECT_BASIC_LIMIT_INFORMATION,
94     IoInfo: IO_COUNTERS,
95     ProcessMemoryLimit: SIZE_T,
96     JobMemoryLimit: SIZE_T,
97     PeakProcessMemoryUsed: SIZE_T,
98     PeakJobMemoryUsed: SIZE_T,
99 }
100
101 #[repr(C)]
102 struct IO_COUNTERS {
103     ReadOperationCount: ULONGLONG,
104     WriteOperationCount: ULONGLONG,
105     OtherOperationCount: ULONGLONG,
106     ReadTransferCount: ULONGLONG,
107     WriteTransferCount: ULONGLONG,
108     OtherTransferCount: ULONGLONG,
109 }
110
111 #[repr(C)]
112 struct JOBOBJECT_BASIC_LIMIT_INFORMATION {
113     PerProcessUserTimeLimit: LARGE_INTEGER,
114     PerJobUserTimeLimit: LARGE_INTEGER,
115     LimitFlags: DWORD,
116     MinimumWorkingsetSize: SIZE_T,
117     MaximumWorkingsetSize: SIZE_T,
118     ActiveProcessLimit: DWORD,
119     Affinity: ULONG_PTR,
120     PriorityClass: DWORD,
121     SchedulingClass: DWORD,
122 }
123
124 pub unsafe fn setup(build: &mut Build) {
125     // Tell Windows to not show any UI on errors (such as not finding a required dll
126     // during startup or terminating abnormally).  This is important for running tests,
127     // since some of them use abnormal termination by design.
128     // This mode is inherited by all child processes.
129     let mode = SetErrorMode(SEM_NOGPFAULTERRORBOX); // read inherited flags
130     SetErrorMode(mode | SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
131
132     // Create a new job object for us to use
133     let job = CreateJobObjectW(0 as *mut _, 0 as *const _);
134     assert!(job != 0 as *mut _, "{}", io::Error::last_os_error());
135
136     // Indicate that when all handles to the job object are gone that all
137     // process in the object should be killed. Note that this includes our
138     // entire process tree by default because we've added ourselves and our
139     // children will reside in the job by default.
140     let mut info = mem::zeroed::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>();
141     info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
142     if build.config.low_priority {
143         info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_PRIORITY_CLASS;
144         info.BasicLimitInformation.PriorityClass = BELOW_NORMAL_PRIORITY_CLASS;
145     }
146     let r = SetInformationJobObject(job,
147                                     JobObjectExtendedLimitInformation,
148                                     &mut info as *mut _ as LPVOID,
149                                     mem::size_of_val(&info) as DWORD);
150     assert!(r != 0, "{}", io::Error::last_os_error());
151
152     // Assign our process to this job object. Note that if this fails, one very
153     // likely reason is that we are ourselves already in a job object! This can
154     // happen on the build bots that we've got for Windows, or if just anyone
155     // else is instrumenting the build. In this case we just bail out
156     // immediately and assume that they take care of it.
157     //
158     // Also note that nested jobs (why this might fail) are supported in recent
159     // versions of Windows, but the version of Windows that our bots are running
160     // at least don't support nested job objects.
161     let r = AssignProcessToJobObject(job, GetCurrentProcess());
162     if r == 0 {
163         CloseHandle(job);
164         return
165     }
166
167     // If we've got a parent process (e.g. the python script that called us)
168     // then move ownership of this job object up to them. That way if the python
169     // script is killed (e.g. via ctrl-c) then we'll all be torn down.
170     //
171     // If we don't have a parent (e.g. this was run directly) then we
172     // intentionally leak the job object handle. When our process exits
173     // (normally or abnormally) it will close the handle implicitly, causing all
174     // processes in the job to be cleaned up.
175     let pid = match env::var("BOOTSTRAP_PARENT_ID") {
176         Ok(s) => s,
177         Err(..) => return,
178     };
179
180     let parent = OpenProcess(PROCESS_DUP_HANDLE, FALSE, pid.parse().unwrap());
181     assert!(parent != 0 as *mut _, "{}", io::Error::last_os_error());
182     let mut parent_handle = 0 as *mut _;
183     let r = DuplicateHandle(GetCurrentProcess(), job,
184                             parent, &mut parent_handle,
185                             0, FALSE, DUPLICATE_SAME_ACCESS);
186
187     // If this failed, well at least we tried! An example of DuplicateHandle
188     // failing in the past has been when the wrong python2 package spawed this
189     // build system (e.g. the `python2` package in MSYS instead of
190     // `mingw-w64-x86_64-python2`. Not sure why it failed, but the "failure
191     // mode" here is that we only clean everything up when the build system
192     // dies, not when the python parent does, so not too bad.
193     if r != 0 {
194         CloseHandle(job);
195     }
196 }