2019-07-26 21:54:25 +00:00
|
|
|
//@ run-pass
|
2017-10-18 01:45:42 +00:00
|
|
|
//@ ignore-emscripten no processes
|
2019-04-24 16:26:33 +00:00
|
|
|
//@ ignore-sgx no processes
|
2017-01-17 07:10:00 +00:00
|
|
|
|
std: Don't pass overlapped handles to processes
This commit fixes a mistake introduced in #31618 where overlapped handles were
leaked to child processes on Windows. On Windows once a handle is in overlapped
mode it should always have I/O executed with an instance of `OVERLAPPED`. Most
child processes, however, are not prepared to have their stdio handles in
overlapped mode as they don't use `OVERLAPPED` on reads/writes to the handle.
Now we haven't had any odd behavior in Rust up to this point, and the original
bug was introduced almost a year ago. I believe this is because it turns out
that if you *don't* pass an `OVERLAPPED` then the system will [supply one for
you][link]. In this case everything will go awry if you concurrently operate on
the handle. In Rust, however, the stdio handles are always locked, and there's
no way to not use them unlocked in libstd. Due to that change we've always had
synchronized access to these handles, which means that Rust programs typically
"just work".
Conversely, though, this commit fixes the test case included, which exhibits
behavior that other programs Rust spawns may attempt to execute. Namely, the
stdio handles may be concurrently used and having them in overlapped mode wreaks
havoc.
[link]: https://blogs.msdn.microsoft.com/oldnewthing/20121012-00/?p=6343
Closes #38811
2017-01-04 23:32:39 +00:00
|
|
|
use std::env;
|
|
|
|
use std::io::prelude::*;
|
|
|
|
use std::process::Command;
|
|
|
|
use std::thread;
|
|
|
|
|
|
|
|
const THREADS: usize = 20;
|
|
|
|
const WRITES: usize = 100;
|
|
|
|
const WRITE_SIZE: usize = 1024 * 32;
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
let args = env::args().collect::<Vec<_>>();
|
|
|
|
if args.len() == 1 {
|
|
|
|
parent();
|
|
|
|
} else {
|
|
|
|
child();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn parent() {
|
|
|
|
let me = env::current_exe().unwrap();
|
|
|
|
let mut cmd = Command::new(me);
|
|
|
|
cmd.arg("run-the-test");
|
|
|
|
let output = cmd.output().unwrap();
|
|
|
|
assert!(output.status.success());
|
|
|
|
assert_eq!(output.stderr.len(), 0);
|
|
|
|
assert_eq!(output.stdout.len(), WRITES * THREADS * WRITE_SIZE);
|
|
|
|
for byte in output.stdout.iter() {
|
|
|
|
assert_eq!(*byte, b'a');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn child() {
|
|
|
|
let threads = (0..THREADS).map(|_| {
|
|
|
|
thread::spawn(|| {
|
|
|
|
let buf = [b'a'; WRITE_SIZE];
|
|
|
|
for _ in 0..WRITES {
|
|
|
|
write_all(&buf);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}).collect::<Vec<_>>();
|
|
|
|
|
|
|
|
for thread in threads {
|
|
|
|
thread.join().unwrap();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(unix)]
|
|
|
|
fn write_all(buf: &[u8]) {
|
|
|
|
use std::fs::File;
|
|
|
|
use std::mem;
|
|
|
|
use std::os::unix::prelude::*;
|
|
|
|
|
|
|
|
let mut file = unsafe { File::from_raw_fd(1) };
|
|
|
|
let res = file.write_all(buf);
|
|
|
|
mem::forget(file);
|
|
|
|
res.unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(windows)]
|
|
|
|
fn write_all(buf: &[u8]) {
|
|
|
|
use std::fs::File;
|
|
|
|
use std::mem;
|
|
|
|
use std::os::windows::raw::*;
|
|
|
|
use std::os::windows::prelude::*;
|
|
|
|
|
|
|
|
const STD_OUTPUT_HANDLE: u32 = (-11i32) as u32;
|
|
|
|
|
|
|
|
extern "system" {
|
|
|
|
fn GetStdHandle(handle: u32) -> HANDLE;
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut file = unsafe {
|
|
|
|
let handle = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
|
|
assert!(!handle.is_null());
|
|
|
|
File::from_raw_handle(handle)
|
|
|
|
};
|
|
|
|
let res = file.write_all(buf);
|
|
|
|
mem::forget(file);
|
|
|
|
res.unwrap();
|
|
|
|
}
|