Skip to content

Commit b68a530

Browse files
authored
Merge pull request #1511 from Seeker14491/job
Fix Windows job management
2 parents 7bf2689 + 5b1af39 commit b68a530

File tree

5 files changed

+82
-198
lines changed

5 files changed

+82
-198
lines changed

src/rustup-cli/job.rs

+32-155
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ pub fn setup() -> Option<Setup> {
2525

2626
#[cfg(unix)]
2727
mod imp {
28+
use std::env;
29+
use libc;
30+
2831
pub type Setup = ();
2932

3033
pub unsafe fn setup() -> Option<()> {
@@ -36,19 +39,23 @@ mod imp {
3639
mod imp {
3740
extern crate winapi;
3841

39-
use std::ffi::OsString;
4042
use std::io;
4143
use std::mem;
42-
use std::os::windows::prelude::*;
43-
use winapi::shared::*;
44-
use winapi::um::*;
44+
use std::ptr;
45+
46+
use self::winapi::shared::minwindef::*;
47+
use self::winapi::um::handleapi::*;
48+
use self::winapi::um::jobapi2::*;
49+
use self::winapi::um::processthreadsapi::*;
50+
use self::winapi::um::winnt::*;
51+
use self::winapi::um::winnt::HANDLE;
4552

4653
pub struct Setup {
4754
job: Handle,
4855
}
4956

5057
pub struct Handle {
51-
inner: ntdef::HANDLE,
58+
inner: HANDLE,
5259
}
5360

5461
fn last_err() -> io::Error {
@@ -65,67 +72,53 @@ mod imp {
6572
// use job objects, so we instead just ignore errors and assume that
6673
// we're otherwise part of someone else's job object in this case.
6774

68-
let job = jobapi2::CreateJobObjectW(0 as *mut _, 0 as *const _);
75+
let job = CreateJobObjectW(ptr::null_mut(), ptr::null());
6976
if job.is_null() {
7077
return None;
7178
}
7279
let job = Handle { inner: job };
7380

7481
// Indicate that when all handles to the job object are gone that all
7582
// process in the object should be killed. Note that this includes our
76-
// entire process tree by default because we've added ourselves and and
83+
// entire process tree by default because we've added ourselves and
7784
// our children will reside in the job once we spawn a process.
78-
let mut info: winnt::JOBOBJECT_EXTENDED_LIMIT_INFORMATION;
85+
let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION;
7986
info = mem::zeroed();
80-
info.BasicLimitInformation.LimitFlags = winnt::JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
81-
let r = jobapi2::SetInformationJobObject(
87+
info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
88+
let r = SetInformationJobObject(
8289
job.inner,
83-
winnt::JobObjectExtendedLimitInformation,
84-
&mut info as *mut _ as minwindef::LPVOID,
85-
mem::size_of_val(&info) as minwindef::DWORD,
90+
JobObjectExtendedLimitInformation,
91+
&mut info as *mut _ as LPVOID,
92+
mem::size_of_val(&info) as DWORD,
8693
);
8794
if r == 0 {
8895
return None;
8996
}
9097

9198
// Assign our process to this job object, meaning that our children will
9299
// now live or die based on our existence.
93-
let me = processthreadsapi::GetCurrentProcess();
94-
let r = jobapi2::AssignProcessToJobObject(job.inner, me);
100+
let me = GetCurrentProcess();
101+
let r = AssignProcessToJobObject(job.inner, me);
95102
if r == 0 {
96103
return None;
97104
}
98105

99-
Some(Setup { job: job })
106+
Some(Setup { job })
100107
}
101108

102109
impl Drop for Setup {
103110
fn drop(&mut self) {
104-
// This is a litte subtle. By default if we are terminated then all
105-
// processes in our job object are terminated as well, but we
106-
// intentionally want to whitelist some processes to outlive our job
107-
// object (see below).
108-
//
109-
// To allow for this, we manually kill processes instead of letting
110-
// the job object kill them for us. We do this in a loop to handle
111-
// processes spawning other processes.
112-
//
113-
// Finally once this is all done we know that the only remaining
114-
// ones are ourselves and the whitelisted processes. The destructor
115-
// here then configures our job object to *not* kill everything on
116-
// close, then closes the job object.
111+
// On normal exits (not ctrl-c), we don't want to kill any child
112+
// processes. The destructor here configures our job object to
113+
// *not* kill everything on close, then closes the job object.
117114
unsafe {
118-
while self.kill_remaining() {
119-
info!("killed some, going for more");
120-
}
121-
122-
let mut info: winnt::JOBOBJECT_EXTENDED_LIMIT_INFORMATION;
115+
let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION;
123116
info = mem::zeroed();
124-
let r = jobapi2::SetInformationJobObject(
117+
let r = SetInformationJobObject(
125118
self.job.inner,
126-
winnt::JobObjectExtendedLimitInformation,
127-
&mut info as *mut _ as minwindef::LPVOID,
128-
mem::size_of_val(&info) as minwindef::DWORD,
119+
JobObjectExtendedLimitInformation,
120+
&mut info as *mut _ as LPVOID,
121+
mem::size_of_val(&info) as DWORD,
129122
);
130123
if r == 0 {
131124
info!("failed to configure job object to defaults: {}", last_err());
@@ -134,126 +127,10 @@ mod imp {
134127
}
135128
}
136129

137-
impl Setup {
138-
unsafe fn kill_remaining(&mut self) -> bool {
139-
#[repr(C)]
140-
struct Jobs {
141-
header: winnt::JOBOBJECT_BASIC_PROCESS_ID_LIST,
142-
list: [basetsd::ULONG_PTR; 1024],
143-
}
144-
145-
let mut jobs: Jobs = mem::zeroed();
146-
let r = jobapi2::QueryInformationJobObject(
147-
self.job.inner,
148-
winnt::JobObjectBasicProcessIdList,
149-
&mut jobs as *mut _ as minwindef::LPVOID,
150-
mem::size_of_val(&jobs) as minwindef::DWORD,
151-
0 as *mut _,
152-
);
153-
if r == 0 {
154-
info!("failed to query job object: {}", last_err());
155-
return false;
156-
}
157-
158-
let mut killed = false;
159-
let list = &jobs.list[..jobs.header.NumberOfProcessIdsInList as usize];
160-
assert!(list.len() > 0);
161-
162-
let list = list.iter()
163-
.filter(|&&id| {
164-
// let's not kill ourselves
165-
id as minwindef::DWORD != processthreadsapi::GetCurrentProcessId()
166-
})
167-
.filter_map(|&id| {
168-
// Open the process with the necessary rights, and if this
169-
// fails then we probably raced with the process exiting so we
170-
// ignore the problem.
171-
let flags = winnt::PROCESS_QUERY_INFORMATION | winnt::PROCESS_TERMINATE
172-
| winnt::SYNCHRONIZE;
173-
let p = processthreadsapi::OpenProcess(
174-
flags,
175-
minwindef::FALSE,
176-
id as minwindef::DWORD,
177-
);
178-
if p.is_null() {
179-
None
180-
} else {
181-
Some(Handle { inner: p })
182-
}
183-
})
184-
.filter(|p| {
185-
// Test if this process was actually in the job object or not.
186-
// If it's not then we likely raced with something else
187-
// recycling this PID, so we just skip this step.
188-
let mut res = 0;
189-
let r = jobapi::IsProcessInJob(p.inner, self.job.inner, &mut res);
190-
if r == 0 {
191-
info!("failed to test is process in job: {}", last_err());
192-
return false;
193-
}
194-
res == minwindef::TRUE
195-
});
196-
197-
for p in list {
198-
// Load the file which this process was spawned from. We then
199-
// later use this for identification purposes.
200-
let mut buf = [0; 1024];
201-
let r = psapi::GetProcessImageFileNameW(
202-
p.inner,
203-
buf.as_mut_ptr(),
204-
buf.len() as minwindef::DWORD,
205-
);
206-
if r == 0 {
207-
info!("failed to get image name: {}", last_err());
208-
continue;
209-
}
210-
let s = OsString::from_wide(&buf[..r as usize]);
211-
info!("found remaining: {:?}", s);
212-
213-
// And here's where we find the whole purpose for this
214-
// function! Currently, our only whitelisted process is
215-
// `mspdbsrv.exe`, and more details about that can be found
216-
// here:
217-
//
218-
// https://github.com/rust-lang/rust/issues/33145
219-
//
220-
// The gist of it is that all builds on one machine use the
221-
// same `mspdbsrv.exe` instance. If we were to kill this
222-
// instance then we could erroneously cause other builds to
223-
// fail.
224-
if let Some(s) = s.to_str() {
225-
if s.contains("mspdbsrv") {
226-
info!("\toops, this is mspdbsrv");
227-
continue;
228-
}
229-
}
230-
231-
// Ok, this isn't mspdbsrv, let's kill the process. After we
232-
// kill it we wait on it to ensure that the next time around in
233-
// this function we're not going to see it again.
234-
let r = processthreadsapi::TerminateProcess(p.inner, 1);
235-
if r == 0 {
236-
info!("\tfailed to kill subprocess: {}", last_err());
237-
info!("\tassuming subprocess is dead...");
238-
} else {
239-
info!("\tterminated subprocess");
240-
}
241-
let r = synchapi::WaitForSingleObject(p.inner, winbase::INFINITE);
242-
if r != 0 {
243-
info!("failed to wait for process to die: {}", last_err());
244-
return false;
245-
}
246-
killed = true;
247-
}
248-
249-
return killed;
250-
}
251-
}
252-
253130
impl Drop for Handle {
254131
fn drop(&mut self) {
255132
unsafe {
256-
handleapi::CloseHandle(self.inner);
133+
CloseHandle(self.inner);
257134
}
258135
}
259136
}

src/rustup-cli/proxy_mode.rs

+34-31
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,54 @@
11
use common::set_globals;
22
use rustup::Cfg;
33
use errors::*;
4-
use rustup_utils::utils;
4+
use rustup_utils::utils::{self, ExitCode};
55
use rustup::command::run_command_for_dir;
66
use std::env;
77
use std::ffi::OsString;
88
use std::path::PathBuf;
9+
use std::process;
910
use job;
1011

1112
pub fn main() -> Result<()> {
1213
::self_update::cleanup_self_updater()?;
1314

14-
let _setup = job::setup();
15-
16-
let mut args = env::args();
17-
18-
let arg0 = args.next().map(PathBuf::from);
19-
let arg0 = arg0.as_ref()
20-
.and_then(|a| a.file_name())
21-
.and_then(|a| a.to_str());
22-
let ref arg0 = arg0.ok_or(ErrorKind::NoExeName)?;
23-
24-
// Check for a toolchain specifier.
25-
let arg1 = args.next();
26-
let toolchain = arg1.as_ref().and_then(|arg1| {
27-
if arg1.starts_with('+') {
28-
Some(&arg1[1..])
15+
let ExitCode(c) = {
16+
let _setup = job::setup();
17+
18+
let mut args = env::args();
19+
20+
let arg0 = args.next().map(PathBuf::from);
21+
let arg0 = arg0.as_ref()
22+
.and_then(|a| a.file_name())
23+
.and_then(|a| a.to_str());
24+
let ref arg0 = arg0.ok_or(ErrorKind::NoExeName)?;
25+
26+
// Check for a toolchain specifier.
27+
let arg1 = args.next();
28+
let toolchain = arg1.as_ref().and_then(|arg1| {
29+
if arg1.starts_with('+') {
30+
Some(&arg1[1..])
31+
} else {
32+
None
33+
}
34+
});
35+
36+
// Build command args now while we know whether or not to skip arg 1.
37+
let cmd_args: Vec<_> = if toolchain.is_none() {
38+
env::args_os().skip(1).collect()
2939
} else {
30-
None
31-
}
32-
});
33-
34-
// Build command args now while we know whether or not to skip arg 1.
35-
let cmd_args: Vec<_> = if toolchain.is_none() {
36-
env::args_os().skip(1).collect()
37-
} else {
38-
env::args_os().skip(2).collect()
39-
};
40+
env::args_os().skip(2).collect()
41+
};
4042

41-
let cfg = set_globals(false)?;
42-
cfg.check_metadata_version()?;
43-
direct_proxy(&cfg, arg0, toolchain, &cmd_args)?;
43+
let cfg = set_globals(false)?;
44+
cfg.check_metadata_version()?;
45+
direct_proxy(&cfg, arg0, toolchain, &cmd_args)?
46+
};
4447

45-
Ok(())
48+
process::exit(c)
4649
}
4750

48-
fn direct_proxy(cfg: &Cfg, arg0: &str, toolchain: Option<&str>, args: &[OsString]) -> Result<()> {
51+
fn direct_proxy(cfg: &Cfg, arg0: &str, toolchain: Option<&str>, args: &[OsString]) -> Result<ExitCode> {
4952
let cmd = match toolchain {
5053
None => cfg.create_command_for_dir(&utils::current_dir()?, arg0)?,
5154
Some(tc) => cfg.create_command_for_toolchain(tc, false, arg0)?,

src/rustup-cli/rustup_mode.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ use rustup::settings::TelemetryMode;
55
use errors::*;
66
use rustup_dist::manifest::Component;
77
use rustup_dist::dist::{PartialTargetTriple, PartialToolchainDesc, TargetTriple};
8-
use rustup_utils::utils;
8+
use rustup_utils::utils::{self, ExitCode};
99
use self_update;
1010
use std::path::Path;
11-
use std::process::Command;
11+
use std::process::{self, Command};
1212
use std::iter;
1313
use std::error::Error;
1414
use term2;
@@ -606,12 +606,14 @@ fn run(cfg: &Cfg, m: &ArgMatches) -> Result<()> {
606606
let args: Vec<_> = args.collect();
607607
let cmd = cfg.create_command_for_toolchain(toolchain, m.is_present("install"), args[0])?;
608608

609-
Ok(command::run_command_for_dir(
609+
let ExitCode(c) = command::run_command_for_dir(
610610
cmd,
611611
args[0],
612612
&args[1..],
613613
&cfg,
614-
)?)
614+
)?;
615+
616+
process::exit(c)
615617
}
616618

617619
fn which(cfg: &Cfg, m: &ArgMatches) -> Result<()> {

src/rustup-utils/src/utils.rs

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ use url::Url;
1919
pub use raw::{find_cmd, has_cmd, if_not_empty, is_directory, is_file, path_exists, prefix_arg,
2020
random_string};
2121

22+
pub struct ExitCode(pub i32);
23+
2224
pub fn ensure_dir_exists(
2325
name: &'static str,
2426
path: &Path,

0 commit comments

Comments
 (0)