1 //! Job management on Windows for bootstrapping
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
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!
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!).
27 //! Note that this module has a #[cfg(windows)] above it as none of this logic
28 //! is required on Unix.
30 #![allow(nonstandard_style, dead_code)]
37 type HANDLE = *mut u8;
40 type LPHANDLE = *mut HANDLE;
41 type LPVOID = *mut u8;
42 type JOBOBJECTINFOCLASS = i32;
44 type LARGE_INTEGER = i64;
46 type ULONG_PTR = usize;
49 const FALSE: BOOL = 0;
50 const DUPLICATE_SAME_ACCESS: DWORD = 0x2;
51 const PROCESS_DUP_HANDLE: DWORD = 0x40;
52 const JobObjectExtendedLimitInformation: JOBOBJECTINFOCLASS = 9;
53 const JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE: DWORD = 0x2000;
54 const JOB_OBJECT_LIMIT_PRIORITY_CLASS: DWORD = 0x00000020;
55 const SEM_FAILCRITICALERRORS: UINT = 0x0001;
56 const SEM_NOGPFAULTERRORBOX: UINT = 0x0002;
57 const BELOW_NORMAL_PRIORITY_CLASS: DWORD = 0x00004000;
60 fn CreateJobObjectW(lpJobAttributes: *mut u8, lpName: *const u8) -> HANDLE;
61 fn CloseHandle(hObject: HANDLE) -> BOOL;
62 fn GetCurrentProcess() -> HANDLE;
63 fn OpenProcess(dwDesiredAccess: DWORD,
65 dwProcessId: DWORD) -> HANDLE;
66 fn DuplicateHandle(hSourceProcessHandle: HANDLE,
67 hSourceHandle: HANDLE,
68 hTargetProcessHandle: HANDLE,
69 lpTargetHandle: LPHANDLE,
70 dwDesiredAccess: DWORD,
72 dwOptions: DWORD) -> BOOL;
73 fn AssignProcessToJobObject(hJob: HANDLE, hProcess: HANDLE) -> BOOL;
74 fn SetInformationJobObject(hJob: HANDLE,
75 JobObjectInformationClass: JOBOBJECTINFOCLASS,
76 lpJobObjectInformation: LPVOID,
77 cbJobObjectInformationLength: DWORD) -> BOOL;
78 fn SetErrorMode(mode: UINT) -> UINT;
82 struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION {
83 BasicLimitInformation: JOBOBJECT_BASIC_LIMIT_INFORMATION,
85 ProcessMemoryLimit: SIZE_T,
86 JobMemoryLimit: SIZE_T,
87 PeakProcessMemoryUsed: SIZE_T,
88 PeakJobMemoryUsed: SIZE_T,
93 ReadOperationCount: ULONGLONG,
94 WriteOperationCount: ULONGLONG,
95 OtherOperationCount: ULONGLONG,
96 ReadTransferCount: ULONGLONG,
97 WriteTransferCount: ULONGLONG,
98 OtherTransferCount: ULONGLONG,
102 struct JOBOBJECT_BASIC_LIMIT_INFORMATION {
103 PerProcessUserTimeLimit: LARGE_INTEGER,
104 PerJobUserTimeLimit: LARGE_INTEGER,
106 MinimumWorkingsetSize: SIZE_T,
107 MaximumWorkingsetSize: SIZE_T,
108 ActiveProcessLimit: DWORD,
110 PriorityClass: DWORD,
111 SchedulingClass: DWORD,
114 pub unsafe fn setup(build: &mut Build) {
115 // Enable the Windows Error Reporting dialog which msys disables,
116 // so we can JIT debug rustc
117 let mode = SetErrorMode(0);
118 SetErrorMode(mode & !SEM_NOGPFAULTERRORBOX);
120 // Create a new job object for us to use
121 let job = CreateJobObjectW(0 as *mut _, 0 as *const _);
122 assert!(job != 0 as *mut _, "{}", io::Error::last_os_error());
124 // Indicate that when all handles to the job object are gone that all
125 // process in the object should be killed. Note that this includes our
126 // entire process tree by default because we've added ourselves and our
127 // children will reside in the job by default.
128 let mut info = mem::zeroed::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>();
129 info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
130 if build.config.low_priority {
131 info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_PRIORITY_CLASS;
132 info.BasicLimitInformation.PriorityClass = BELOW_NORMAL_PRIORITY_CLASS;
134 let r = SetInformationJobObject(job,
135 JobObjectExtendedLimitInformation,
136 &mut info as *mut _ as LPVOID,
137 mem::size_of_val(&info) as DWORD);
138 assert!(r != 0, "{}", io::Error::last_os_error());
140 // Assign our process to this job object. Note that if this fails, one very
141 // likely reason is that we are ourselves already in a job object! This can
142 // happen on the build bots that we've got for Windows, or if just anyone
143 // else is instrumenting the build. In this case we just bail out
144 // immediately and assume that they take care of it.
146 // Also note that nested jobs (why this might fail) are supported in recent
147 // versions of Windows, but the version of Windows that our bots are running
148 // at least don't support nested job objects.
149 let r = AssignProcessToJobObject(job, GetCurrentProcess());
155 // If we've got a parent process (e.g., the python script that called us)
156 // then move ownership of this job object up to them. That way if the python
157 // script is killed (e.g., via ctrl-c) then we'll all be torn down.
159 // If we don't have a parent (e.g., this was run directly) then we
160 // intentionally leak the job object handle. When our process exits
161 // (normally or abnormally) it will close the handle implicitly, causing all
162 // processes in the job to be cleaned up.
163 let pid = match env::var("BOOTSTRAP_PARENT_ID") {
168 let parent = OpenProcess(PROCESS_DUP_HANDLE, FALSE, pid.parse().unwrap());
169 assert!(parent != 0 as *mut _, "{}", io::Error::last_os_error());
170 let mut parent_handle = 0 as *mut _;
171 let r = DuplicateHandle(GetCurrentProcess(), job,
172 parent, &mut parent_handle,
173 0, FALSE, DUPLICATE_SAME_ACCESS);
175 // If this failed, well at least we tried! An example of DuplicateHandle
176 // failing in the past has been when the wrong python2 package spawned this
177 // build system (e.g., the `python2` package in MSYS instead of
178 // `mingw-w64-x86_64-python2`. Not sure why it failed, but the "failure
179 // mode" here is that we only clean everything up when the build system
180 // dies, not when the python parent does, so not too bad.