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)]
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
,
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
);
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());
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
;
69 let r
= SetInformationJobObject(
71 JobObjectExtendedLimitInformation
,
72 &mut info
as *mut _
as LPVOID
,
73 mem
::size_of_val(&info
) as DWORD
,
75 assert
!(r
!= 0, "{}", io
::Error
::last_os_error());
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.
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());
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.
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") {
105 let parent
= OpenProcess(PROCESS_DUP_HANDLE
, FALSE
, pid
.parse().unwrap());
108 "PID `{}` doesn't seem to exist: {}",
110 io
::Error
::last_os_error()
112 let mut parent_handle
= ptr
::null_mut();
113 let r
= DuplicateHandle(
120 DUPLICATE_SAME_ACCESS
,
123 // If this failed, well at least we tried! An example of DuplicateHandle
124 // failing in the past has been when the wrong python2 package spawned this
125 // build system (e.g., the `python2` package in MSYS instead of
126 // `mingw-w64-x86_64-python2`. Not sure why it failed, but the "failure
127 // mode" here is that we only clean everything up when the build system
128 // dies, not when the python parent does, so not too bad.