]>
Commit | Line | Data |
---|---|---|
7453a54e SL |
1 | //! Job management on Windows for bootstrapping |
2 | //! | |
0731742a | 3 | //! Most of the time when you're running a build system (e.g., make) you expect |
7453a54e SL |
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 | ||
b7449926 | 30 | #![allow(nonstandard_style, dead_code)] |
7453a54e | 31 | |
dfeec247 | 32 | use crate::Build; |
7453a54e SL |
33 | use std::env; |
34 | use std::io; | |
35 | use std::mem; | |
dc9dc135 | 36 | use std::ptr; |
476ff2be | 37 | |
dfeec247 XL |
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 | }; | |
7453a54e | 48 | |
7cac9316 | 49 | pub unsafe fn setup(build: &mut Build) { |
83c7162d XL |
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); | |
476ff2be | 54 | |
7453a54e | 55 | // Create a new job object for us to use |
dc9dc135 XL |
56 | let job = CreateJobObjectW(ptr::null_mut(), ptr::null()); |
57 | assert!(!job.is_null(), "{}", io::Error::last_os_error()); | |
7453a54e SL |
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 | |
a7813a04 | 61 | // entire process tree by default because we've added ourselves and our |
7453a54e SL |
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; | |
7cac9316 XL |
65 | if build.config.low_priority { |
66 | info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_PRIORITY_CLASS; | |
67 | info.BasicLimitInformation.PriorityClass = BELOW_NORMAL_PRIORITY_CLASS; | |
68 | } | |
dfeec247 XL |
69 | let r = SetInformationJobObject( |
70 | job, | |
71 | JobObjectExtendedLimitInformation, | |
72 | &mut info as *mut _ as LPVOID, | |
73 | mem::size_of_val(&info) as DWORD, | |
74 | ); | |
7453a54e SL |
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); | |
dfeec247 | 89 | return; |
7453a54e SL |
90 | } |
91 | ||
0731742a | 92 | // If we've got a parent process (e.g., the python script that called us) |
7453a54e | 93 | // then move ownership of this job object up to them. That way if the python |
0731742a | 94 | // script is killed (e.g., via ctrl-c) then we'll all be torn down. |
7453a54e | 95 | // |
0731742a | 96 | // If we don't have a parent (e.g., this was run directly) then we |
7453a54e SL |
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()); | |
136023e0 XL |
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 | ||
dc9dc135 | 120 | let mut parent_handle = ptr::null_mut(); |
dfeec247 XL |
121 | let r = DuplicateHandle( |
122 | GetCurrentProcess(), | |
123 | job, | |
124 | parent, | |
125 | &mut parent_handle, | |
126 | 0, | |
127 | FALSE, | |
128 | DUPLICATE_SAME_ACCESS, | |
129 | ); | |
7453a54e SL |
130 | |
131 | // If this failed, well at least we tried! An example of DuplicateHandle | |
ff7c6d11 | 132 | // failing in the past has been when the wrong python2 package spawned this |
0731742a | 133 | // build system (e.g., the `python2` package in MSYS instead of |
7453a54e SL |
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 | } |