Skip to content

Commit 17355a3

Browse files
committed
Auto merge of #98950 - ChrisDenton:getoverlapped-io, r=thomcc
Windows: Fallback for overlapped I/O Fixes #98947
2 parents 6dba4ed + 91a6401 commit 17355a3

File tree

4 files changed

+134
-8
lines changed

4 files changed

+134
-8
lines changed

library/std/src/sys/windows/c.rs

+13-1
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,9 @@ union IO_STATUS_BLOCK_union {
326326
}
327327
impl Default for IO_STATUS_BLOCK_union {
328328
fn default() -> Self {
329-
Self { Pointer: ptr::null_mut() }
329+
let mut this = Self { Pointer: ptr::null_mut() };
330+
this.Status = STATUS_PENDING;
331+
this
330332
}
331333
}
332334
#[repr(C)]
@@ -335,6 +337,16 @@ pub struct IO_STATUS_BLOCK {
335337
u: IO_STATUS_BLOCK_union,
336338
pub Information: usize,
337339
}
340+
impl IO_STATUS_BLOCK {
341+
pub fn status(&self) -> NTSTATUS {
342+
// SAFETY: If `self.u.Status` was set then this is obviously safe.
343+
// If `self.u.Pointer` was set then this is the equivalent to converting
344+
// the pointer to an integer, which is also safe.
345+
// Currently the only safe way to construct `IO_STATUS_BLOCK` outside of
346+
// this module is to call the `default` method, which sets the `Status`.
347+
unsafe { self.u.Status }
348+
}
349+
}
338350

339351
pub type LPOVERLAPPED_COMPLETION_ROUTINE = unsafe extern "system" fn(
340352
dwErrorCode: DWORD,

library/std/src/sys/windows/handle.rs

+18-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
#![unstable(issue = "none", feature = "windows_handle")]
22

3+
#[cfg(test)]
4+
mod tests;
5+
36
use crate::cmp;
47
use crate::io::{self, ErrorKind, IoSlice, IoSliceMut, Read, ReadBuf};
58
use crate::mem;
@@ -248,14 +251,18 @@ impl Handle {
248251
offset.map(|n| n as _).as_ref(),
249252
None,
250253
);
254+
255+
let status = if status == c::STATUS_PENDING {
256+
c::WaitForSingleObject(self.as_raw_handle(), c::INFINITE);
257+
io_status.status()
258+
} else {
259+
status
260+
};
251261
match status {
252262
// If the operation has not completed then abort the process.
253263
// Doing otherwise means that the buffer and stack may be written to
254264
// after this function returns.
255-
c::STATUS_PENDING => {
256-
eprintln!("I/O error: operation failed to complete synchronously");
257-
crate::process::abort();
258-
}
265+
c::STATUS_PENDING => rtabort!("I/O error: operation failed to complete synchronously"),
259266

260267
// Return `Ok(0)` when there's nothing more to read.
261268
c::STATUS_END_OF_FILE => Ok(0),
@@ -294,13 +301,17 @@ impl Handle {
294301
None,
295302
)
296303
};
304+
let status = if status == c::STATUS_PENDING {
305+
unsafe { c::WaitForSingleObject(self.as_raw_handle(), c::INFINITE) };
306+
io_status.status()
307+
} else {
308+
status
309+
};
297310
match status {
298311
// If the operation has not completed then abort the process.
299312
// Doing otherwise means that the buffer may be read and the stack
300313
// written to after this function returns.
301-
c::STATUS_PENDING => {
302-
rtabort!("I/O error: operation failed to complete synchronously");
303-
}
314+
c::STATUS_PENDING => rtabort!("I/O error: operation failed to complete synchronously"),
304315

305316
// Success!
306317
status if c::nt_success(status) => Ok(io_status.Information),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
use crate::sys::pipe::{anon_pipe, Pipes};
2+
use crate::{thread, time};
3+
4+
/// Test the synchronous fallback for overlapped I/O.
5+
#[test]
6+
fn overlapped_handle_fallback() {
7+
// Create some pipes. `ours` will be asynchronous.
8+
let Pipes { ours, theirs } = anon_pipe(true, false).unwrap();
9+
10+
let async_readable = ours.into_handle();
11+
let sync_writeable = theirs.into_handle();
12+
13+
thread::scope(|_| {
14+
thread::sleep(time::Duration::from_millis(100));
15+
sync_writeable.write(b"hello world!").unwrap();
16+
});
17+
18+
// The pipe buffer starts empty so reading won't complete synchronously unless
19+
// our fallback path works.
20+
let mut buffer = [0u8; 1024];
21+
async_readable.read(&mut buffer).unwrap();
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// run-fail
2+
// only-windows
3+
4+
fn main() {
5+
use std::fs;
6+
use std::io::prelude::*;
7+
use std::os::windows::prelude::*;
8+
use std::ptr;
9+
use std::sync::Arc;
10+
use std::thread;
11+
use std::time::Duration;
12+
13+
const FILE_FLAG_OVERLAPPED: u32 = 0x40000000;
14+
15+
fn create_pipe_server(path: &str) -> fs::File {
16+
let mut path0 = path.as_bytes().to_owned();
17+
path0.push(0);
18+
extern "system" {
19+
fn CreateNamedPipeA(
20+
lpName: *const u8,
21+
dwOpenMode: u32,
22+
dwPipeMode: u32,
23+
nMaxInstances: u32,
24+
nOutBufferSize: u32,
25+
nInBufferSize: u32,
26+
nDefaultTimeOut: u32,
27+
lpSecurityAttributes: *mut u8,
28+
) -> RawHandle;
29+
}
30+
31+
unsafe {
32+
let h = CreateNamedPipeA(path0.as_ptr(), 3, 0, 1, 0, 0, 0, ptr::null_mut());
33+
assert_ne!(h as isize, -1);
34+
fs::File::from_raw_handle(h)
35+
}
36+
}
37+
38+
let path = "\\\\.\\pipe\\repro";
39+
let mut server = create_pipe_server(path);
40+
41+
let client = Arc::new(
42+
fs::OpenOptions::new().custom_flags(FILE_FLAG_OVERLAPPED).read(true).open(path).unwrap(),
43+
);
44+
45+
let spawn_read = |is_first: bool| {
46+
thread::spawn({
47+
let f = client.clone();
48+
move || {
49+
let mut buf = [0xcc; 1];
50+
let mut f = f.as_ref();
51+
f.read(&mut buf).unwrap();
52+
if is_first {
53+
assert_ne!(buf[0], 0xcc);
54+
} else {
55+
let b = buf[0]; // capture buf[0]
56+
thread::sleep(Duration::from_millis(200));
57+
58+
// Check the buffer hasn't been written to after read.
59+
dbg!(buf[0], b);
60+
assert_eq!(buf[0], b);
61+
}
62+
}
63+
})
64+
};
65+
66+
let t1 = spawn_read(true);
67+
thread::sleep(Duration::from_millis(20));
68+
let t2 = spawn_read(false);
69+
thread::sleep(Duration::from_millis(100));
70+
let _ = server.write(b"x");
71+
thread::sleep(Duration::from_millis(100));
72+
let _ = server.write(b"y");
73+
74+
// This is run fail because we need to test for the `abort`.
75+
// That failing to run is the success case.
76+
if t1.join().is_err() || t2.join().is_err() {
77+
return;
78+
} else {
79+
panic!("success");
80+
}
81+
}

0 commit comments

Comments
 (0)