Change:
on windows. with and rename it back to git bash I expected to see this happen: my really long string gets printed twice, followed by a clean exit Instead, this happened: some amount of my really long string gets printed, followed by: The test case, while simple, does appear to have a minimum size requirement to trigger this crash. Shrinking the string will cause it to stop crashing. I'm using in the steps for simplicity, but we've observed this happening with other programs at the end of the pipe too. Redirecting to a file instead of a pipe seems to not trigger this crash. Using is not necessary to trigger the crash, running the built executable directly also crashes. This PR this new error condition: I am not the only one seeing this new error message: modify labels: +regression-from-stable-to-stable +O-windows
I believe this must be because git bash (cygwin? msys?) is opening pipes between processes with . In my original bug report, I suggested calling once if returned , since that will at least resolve the pending IO correctly in the case where the file is not being used for other IOs concurrently. This would cover this and other cases at least as well as the original code calling . This isn't perfect, since of course takes , so you can probably deadlock in a multi-threaded process in this async pipe configuration. But it would at least avoid the common case. However, it seems that this got missed from the actual change (sorry, I should have caught that). Is there a reason this was skipped?
Oh, hrm. That looks like an oversight on my part. I'll PR a fix asap.
Thanks for fixing the issue quickly. When the PR that fixes this issue is merged, will a patch release 1.62.1 be published? Or will we have to wait for a fix in 1.63?
PR was nominated and accepted for both beta and stable backport by T-libs. Leaving this open until those play out. Assigning P-high for the same reasons it was accepted for backports.

Discussion:
} impl Default for IO_STATUS_BLOCK_union { fn default() -> Self { <del> Self { Pointer: ptr::null_mut() } </del> <ins> let mut this = Self { Pointer: ptr::null_mut() }; this.Status = STATUS_PENDING; this </ins> } } #[repr(C)]

u: IO_STATUS_BLOCK_union, pub Information: usize, } <ins> impl IO_STATUS_BLOCK { pub fn status(&self) -> NTSTATUS { // SAFETY: If `self.u.Status` was set then this is obviously safe. // If `self.u.Pointer` was set then this is the equivalent to converting // the pointer to an integer, which is also safe. // Currently the only safe way to construct `IO_STATUS_BLOCK` outside of // this module is to call the `default` method, which sets the `Status`. unsafe { self.u.Status } } } </ins> pub type LPOVERLAPPED_COMPLETION_ROUTINE = unsafe extern "system" fn( dwErrorCode: DWORD,

#![unstable(issue = "none", feature = "windows_handle")] <ins> #[cfg(test)] mod tests; </ins> use crate::cmp; use crate::io::{self, ErrorKind, IoSlice, IoSliceMut, Read, ReadBuf}; use crate::mem;

offset.map(|n| n as _).as_ref(), None, ); <ins> let status = if status == c::STATUS_PENDING { c::WaitForSingleObject(self.as_raw_handle(), c::INFINITE); io_status.status() } else { status }; </ins> match status { // If the operation has not completed then abort the process. // Doing otherwise means that the buffer and stack may be written to // after this function returns. <del> c::STATUS_PENDING => { eprintln!("I/O error: operation failed to complete synchronously"); crate::process::abort(); } </del> <ins> c::STATUS_PENDING => rtabort!("I/O error: operation failed to complete synchronously"), </ins> // Return `Ok(0)` when there's nothing more to read. c::STATUS_END_OF_FILE => Ok(0),

None, ) }; <ins> let status = if status == c::STATUS_PENDING { unsafe { c::WaitForSingleObject(self.as_raw_handle(), c::INFINITE) }; io_status.status() } else { status }; </ins> match status { // If the operation has not completed then abort the process. // Doing otherwise means that the buffer may be read and the stack // written to after this function returns. <del> c::STATUS_PENDING => { rtabort!("I/O error: operation failed to complete synchronously"); } </del> <ins> c::STATUS_PENDING => rtabort!("I/O error: operation failed to complete synchronously"), </ins> // Success! status if c::nt_success(status) => Ok(io_status.Information),

<ins> use crate::sys::pipe::{anon_pipe, Pipes}; use crate::{thread, time}; /// Test the synchronous fallback for overlapped I/O. #[test] fn overlapped_handle_fallback() { // Create some pipes. `ours` will be asynchronous. let Pipes { ours, theirs } = anon_pipe(true, false).unwrap(); let async_readable = ours.into_handle(); let sync_writeable = theirs.into_handle(); thread::scope(|_| { thread::sleep(time::Duration::from_millis(100)); sync_writeable.write(b"hello world!").unwrap(); }); // The pipe buffer starts empty so reading won't complete synchronously unless // our fallback path works. let mut buffer = [0u8; 1024]; async_readable.read(&mut buffer).unwrap(); } </ins>

<ins> // run-fail // only-windows fn main() { use std::fs; use std::io::prelude::*; use std::os::windows::prelude::*; use std::ptr; use std::sync::Arc; use std::thread; use std::time::Duration; const FILE_FLAG_OVERLAPPED: u32 = 0x40000000; fn create_pipe_server(path: &str) -> fs::File { let mut path0 = path.as_bytes().to_owned(); path0.push(0); extern "system" { fn CreateNamedPipeA( lpName: *const u8, dwOpenMode: u32, dwPipeMode: u32, nMaxInstances: u32, nOutBufferSize: u32, nInBufferSize: u32, nDefaultTimeOut: u32, lpSecurityAttributes: *mut u8, ) -> RawHandle; } unsafe { let h = CreateNamedPipeA(path0.as_ptr(), 3, 0, 1, 0, 0, 0, ptr::null_mut()); assert_ne!(h as isize, -1); fs::File::from_raw_handle(h) } } let path = ".piperepro"; let mut server = create_pipe_server(path); let client = Arc::new( fs::OpenOptions::new().custom_flags(FILE_FLAG_OVERLAPPED).read(true).open(path).unwrap(), ); let spawn_read = |is_first: bool| { thread::spawn({ let f = client.clone(); move || { let mut buf = [0xcc; 1]; let mut f = f.as_ref(); f.read(&mut buf).unwrap(); if is_first { assert_ne!(buf[0], 0xcc); } else { let b = buf[0]; // capture buf[0] thread::sleep(Duration::from_millis(200)); // Check the buffer hasn't been written to after read. dbg!(buf[0], b); assert_eq!(buf[0], b); } } }) }; let t1 = spawn_read(true); thread::sleep(Duration::from_millis(20)); let t2 = spawn_read(false); thread::sleep(Duration::from_millis(100)); let _ = server.write(b"x"); thread::sleep(Duration::from_millis(100)); let _ = server.write(b"y"); // This is run fail because we need to test for the `abort`. // That failing to run is the success case. if t1.join().is_err() || t2.join().is_err() { return; } else { panic!("success"); } } </ins>