Skip to content

Commit 73e77cb

Browse files
committed
merge target specific job implementations in job module
Signed-off-by: onur-ozkan <[email protected]>
1 parent 484d681 commit 73e77cb

File tree

2 files changed

+156
-144
lines changed

2 files changed

+156
-144
lines changed

src/bootstrap/src/misc/job.rs

+155-127
Original file line numberDiff line numberDiff line change
@@ -1,143 +1,171 @@
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.
1+
#[cfg(any(target_os = "haiku", target_os = "hermit", not(any(unix, windows))))]
2+
pub use custom::*;
293

30-
use crate::Build;
31-
use std::env;
32-
use std::ffi::c_void;
33-
use std::io;
34-
use std::mem;
4+
#[cfg(all(unix, not(target_os = "haiku")))]
5+
pub use unix::*;
356

36-
use windows::{
37-
core::PCWSTR,
38-
Win32::Foundation::{CloseHandle, DuplicateHandle, DUPLICATE_SAME_ACCESS, HANDLE},
39-
Win32::System::Diagnostics::Debug::{SetErrorMode, SEM_NOGPFAULTERRORBOX, THREAD_ERROR_MODE},
40-
Win32::System::JobObjects::{
41-
AssignProcessToJobObject, CreateJobObjectW, JobObjectExtendedLimitInformation,
42-
SetInformationJobObject, JOBOBJECT_EXTENDED_LIMIT_INFORMATION,
43-
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, JOB_OBJECT_LIMIT_PRIORITY_CLASS,
44-
},
45-
Win32::System::Threading::{
46-
GetCurrentProcess, OpenProcess, BELOW_NORMAL_PRIORITY_CLASS, PROCESS_DUP_HANDLE,
47-
},
48-
};
7+
#[cfg(windows)]
8+
pub use windows::*;
499

50-
pub unsafe fn setup(build: &mut Build) {
51-
// Enable the Windows Error Reporting dialog which msys disables,
52-
// so we can JIT debug rustc
53-
let mode = SetErrorMode(THREAD_ERROR_MODE::default());
54-
let mode = THREAD_ERROR_MODE(mode);
55-
SetErrorMode(mode & !SEM_NOGPFAULTERRORBOX);
56-
57-
// Create a new job object for us to use
58-
let job = CreateJobObjectW(None, PCWSTR::null()).unwrap();
10+
#[cfg(any(target_os = "haiku", target_os = "hermit", not(any(unix, windows))))]
11+
mod custom {
12+
pub unsafe fn setup(_build: &mut crate::Build) {}
13+
}
5914

60-
// Indicate that when all handles to the job object are gone that all
61-
// process in the object should be killed. Note that this includes our
62-
// entire process tree by default because we've added ourselves and our
63-
// children will reside in the job by default.
64-
let mut info = JOBOBJECT_EXTENDED_LIMIT_INFORMATION::default();
65-
info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
66-
if build.config.low_priority {
67-
info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_PRIORITY_CLASS;
68-
info.BasicLimitInformation.PriorityClass = BELOW_NORMAL_PRIORITY_CLASS.0;
15+
#[cfg(all(unix, not(target_os = "haiku")))]
16+
mod unix {
17+
pub unsafe fn setup(build: &mut crate::Build) {
18+
if build.config.low_priority {
19+
libc::setpriority(libc::PRIO_PGRP as _, 0, 10);
20+
}
6921
}
70-
let r = SetInformationJobObject(
71-
job,
72-
JobObjectExtendedLimitInformation,
73-
&info as *const _ as *const c_void,
74-
mem::size_of_val(&info) as u32,
75-
)
76-
.ok();
77-
assert!(r.is_ok(), "{}", io::Error::last_os_error());
22+
}
7823

79-
// Assign our process to this job object. Note that if this fails, one very
80-
// likely reason is that we are ourselves already in a job object! This can
81-
// happen on the build bots that we've got for Windows, or if just anyone
82-
// else is instrumenting the build. In this case we just bail out
83-
// immediately and assume that they take care of it.
84-
//
85-
// Also note that nested jobs (why this might fail) are supported in recent
86-
// versions of Windows, but the version of Windows that our bots are running
87-
// at least don't support nested job objects.
88-
let r = AssignProcessToJobObject(job, GetCurrentProcess()).ok();
89-
if r.is_err() {
90-
CloseHandle(job);
91-
return;
92-
}
24+
#[cfg(windows)]
25+
mod windows {
26+
//! Job management on Windows for bootstrapping
27+
//!
28+
//! Most of the time when you're running a build system (e.g., make) you expect
29+
//! Ctrl-C or abnormal termination to actually terminate the entire tree of
30+
//! process in play, not just the one at the top. This currently works "by
31+
//! default" on Unix platforms because Ctrl-C actually sends a signal to the
32+
//! *process group* rather than the parent process, so everything will get torn
33+
//! down. On Windows, however, this does not happen and Ctrl-C just kills the
34+
//! parent process.
35+
//!
36+
//! To achieve the same semantics on Windows we use Job Objects to ensure that
37+
//! all processes die at the same time. Job objects have a mode of operation
38+
//! where when all handles to the object are closed it causes all child
39+
//! processes associated with the object to be terminated immediately.
40+
//! Conveniently whenever a process in the job object spawns a new process the
41+
//! child will be associated with the job object as well. This means if we add
42+
//! ourselves to the job object we create then everything will get torn down!
43+
//!
44+
//! Unfortunately most of the time the build system is actually called from a
45+
//! python wrapper (which manages things like building the build system) so this
46+
//! all doesn't quite cut it so far. To go the last mile we duplicate the job
47+
//! object handle into our parent process (a python process probably) and then
48+
//! close our own handle. This means that the only handle to the job object
49+
//! resides in the parent python process, so when python dies the whole build
50+
//! system dies (as one would probably expect!).
51+
//!
52+
//! Note that this module has a #[cfg(windows)] above it as none of this logic
53+
//! is required on Unix.
54+
55+
use crate::Build;
56+
use std::env;
57+
use std::ffi::c_void;
58+
use std::io;
59+
use std::mem;
9360

94-
// If we've got a parent process (e.g., the python script that called us)
95-
// then move ownership of this job object up to them. That way if the python
96-
// script is killed (e.g., via ctrl-c) then we'll all be torn down.
97-
//
98-
// If we don't have a parent (e.g., this was run directly) then we
99-
// intentionally leak the job object handle. When our process exits
100-
// (normally or abnormally) it will close the handle implicitly, causing all
101-
// processes in the job to be cleaned up.
102-
let pid = match env::var("BOOTSTRAP_PARENT_ID") {
103-
Ok(s) => s,
104-
Err(..) => return,
61+
use windows::{
62+
core::PCWSTR,
63+
Win32::Foundation::{CloseHandle, DuplicateHandle, DUPLICATE_SAME_ACCESS, HANDLE},
64+
Win32::System::Diagnostics::Debug::{
65+
SetErrorMode, SEM_NOGPFAULTERRORBOX, THREAD_ERROR_MODE,
66+
},
67+
Win32::System::JobObjects::{
68+
AssignProcessToJobObject, CreateJobObjectW, JobObjectExtendedLimitInformation,
69+
SetInformationJobObject, JOBOBJECT_EXTENDED_LIMIT_INFORMATION,
70+
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, JOB_OBJECT_LIMIT_PRIORITY_CLASS,
71+
},
72+
Win32::System::Threading::{
73+
GetCurrentProcess, OpenProcess, BELOW_NORMAL_PRIORITY_CLASS, PROCESS_DUP_HANDLE,
74+
},
10575
};
10676

107-
let parent = match OpenProcess(PROCESS_DUP_HANDLE, false, pid.parse().unwrap()).ok() {
108-
Some(parent) => parent,
109-
_ => {
110-
// If we get a null parent pointer here, it is possible that either
111-
// we have an invalid pid or the parent process has been closed.
112-
// Since the first case rarely happens
113-
// (only when wrongly setting the environmental variable),
114-
// it might be better to improve the experience of the second case
115-
// when users have interrupted the parent process and we haven't finish
116-
// duplicating the handle yet. We just need close the job object if that occurs.
77+
pub unsafe fn setup(build: &mut Build) {
78+
// Enable the Windows Error Reporting dialog which msys disables,
79+
// so we can JIT debug rustc
80+
let mode = SetErrorMode(THREAD_ERROR_MODE::default());
81+
let mode = THREAD_ERROR_MODE(mode);
82+
SetErrorMode(mode & !SEM_NOGPFAULTERRORBOX);
83+
84+
// Create a new job object for us to use
85+
let job = CreateJobObjectW(None, PCWSTR::null()).unwrap();
86+
87+
// Indicate that when all handles to the job object are gone that all
88+
// process in the object should be killed. Note that this includes our
89+
// entire process tree by default because we've added ourselves and our
90+
// children will reside in the job by default.
91+
let mut info = JOBOBJECT_EXTENDED_LIMIT_INFORMATION::default();
92+
info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
93+
if build.config.low_priority {
94+
info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_PRIORITY_CLASS;
95+
info.BasicLimitInformation.PriorityClass = BELOW_NORMAL_PRIORITY_CLASS.0;
96+
}
97+
let r = SetInformationJobObject(
98+
job,
99+
JobObjectExtendedLimitInformation,
100+
&info as *const _ as *const c_void,
101+
mem::size_of_val(&info) as u32,
102+
)
103+
.ok();
104+
assert!(r.is_ok(), "{}", io::Error::last_os_error());
105+
106+
// Assign our process to this job object. Note that if this fails, one very
107+
// likely reason is that we are ourselves already in a job object! This can
108+
// happen on the build bots that we've got for Windows, or if just anyone
109+
// else is instrumenting the build. In this case we just bail out
110+
// immediately and assume that they take care of it.
111+
//
112+
// Also note that nested jobs (why this might fail) are supported in recent
113+
// versions of Windows, but the version of Windows that our bots are running
114+
// at least don't support nested job objects.
115+
let r = AssignProcessToJobObject(job, GetCurrentProcess()).ok();
116+
if r.is_err() {
117117
CloseHandle(job);
118118
return;
119119
}
120-
};
121120

122-
let mut parent_handle = HANDLE::default();
123-
let r = DuplicateHandle(
124-
GetCurrentProcess(),
125-
job,
126-
parent,
127-
&mut parent_handle,
128-
0,
129-
false,
130-
DUPLICATE_SAME_ACCESS,
131-
)
132-
.ok();
121+
// If we've got a parent process (e.g., the python script that called us)
122+
// then move ownership of this job object up to them. That way if the python
123+
// script is killed (e.g., via ctrl-c) then we'll all be torn down.
124+
//
125+
// If we don't have a parent (e.g., this was run directly) then we
126+
// intentionally leak the job object handle. When our process exits
127+
// (normally or abnormally) it will close the handle implicitly, causing all
128+
// processes in the job to be cleaned up.
129+
let pid = match env::var("BOOTSTRAP_PARENT_ID") {
130+
Ok(s) => s,
131+
Err(..) => return,
132+
};
133+
134+
let parent = match OpenProcess(PROCESS_DUP_HANDLE, false, pid.parse().unwrap()).ok() {
135+
Some(parent) => parent,
136+
_ => {
137+
// If we get a null parent pointer here, it is possible that either
138+
// we have an invalid pid or the parent process has been closed.
139+
// Since the first case rarely happens
140+
// (only when wrongly setting the environmental variable),
141+
// it might be better to improve the experience of the second case
142+
// when users have interrupted the parent process and we haven't finish
143+
// duplicating the handle yet. We just need close the job object if that occurs.
144+
CloseHandle(job);
145+
return;
146+
}
147+
};
133148

134-
// If this failed, well at least we tried! An example of DuplicateHandle
135-
// failing in the past has been when the wrong python2 package spawned this
136-
// build system (e.g., the `python2` package in MSYS instead of
137-
// `mingw-w64-x86_64-python2`). Not sure why it failed, but the "failure
138-
// mode" here is that we only clean everything up when the build system
139-
// dies, not when the python parent does, so not too bad.
140-
if r.is_err() {
141-
CloseHandle(job);
149+
let mut parent_handle = HANDLE::default();
150+
let r = DuplicateHandle(
151+
GetCurrentProcess(),
152+
job,
153+
parent,
154+
&mut parent_handle,
155+
0,
156+
false,
157+
DUPLICATE_SAME_ACCESS,
158+
)
159+
.ok();
160+
161+
// If this failed, well at least we tried! An example of DuplicateHandle
162+
// failing in the past has been when the wrong python2 package spawned this
163+
// build system (e.g., the `python2` package in MSYS instead of
164+
// `mingw-w64-x86_64-python2`). Not sure why it failed, but the "failure
165+
// mode" here is that we only clean everything up when the build system
166+
// dies, not when the python parent does, so not too bad.
167+
if r.is_err() {
168+
CloseHandle(job);
169+
}
142170
}
143171
}

src/bootstrap/src/misc/mod.rs

+1-17
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,4 @@
11
pub(crate) mod cache;
2-
pub(crate) mod channel;
32
pub(crate) mod cc_detect;
4-
5-
#[cfg(windows)]
3+
pub(crate) mod channel;
64
pub(crate) mod job;
7-
8-
#[cfg(all(unix, not(target_os = "haiku")))]
9-
pub(crate) mod job {
10-
pub unsafe fn setup(build: &mut crate::Build) {
11-
if build.config.low_priority {
12-
libc::setpriority(libc::PRIO_PGRP as _, 0, 10);
13-
}
14-
}
15-
}
16-
17-
#[cfg(any(target_os = "haiku", target_os = "hermit", not(any(unix, windows))))]
18-
pub(crate) mod job {
19-
pub unsafe fn setup(_build: &mut crate::Build) {}
20-
}

0 commit comments

Comments
 (0)