windows: `TcpStream::shutdown` does not wake up blocking reads
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 119k
- Forks
- 16.1k
- PR merge metrics
- PR metrics pending
Description
Description
The docs for TcpStream::shutdown currently guarantee that a shutdown with Shutdown::Read must wake up pending operations immediately:
This function will cause all pending and future I/O on the specified portions to return immediately with an appropriate value (see the documentation of Shutdown).
The docs for Shutdown::Read explicitly says that currently blocked reads must return:
However, that is currently not the case on *-pc-windows-* platforms. Instead, a currently blocked read will stay blocked forever when shutdown is called with Shutdown::Read in a different thread. The same also happens with Shutdown::Both.
This is causing the close_read_wakes_up test to fail spuriously due to a race condition, for example in https://github.com/rust-lang/rust/pull/121523#issuecomment-1962581666 and https://github.com/rust-lang/rust/pull/120543#issuecomment-1925748911. The test only passes if the shutdown happens before the read.
Repro
I tried this code:
use std::io::Read;
use std::net::{Ipv6Addr, Shutdown, TcpListener, TcpStream};
use std::thread;
use std::time::Duration;
fn main() {
let listener = TcpListener::bind((Ipv6Addr::LOCALHOST, 0)).unwrap();
let listener_addr = listener.local_addr().unwrap();
let mut serverbound_stream = None;
thread::scope(|scope| {
// 1. `accept` and `connect` concurrently
let _clientbound_stream = scope.spawn(|| {
let (clientbound_stream, _) = listener.accept().unwrap();
clientbound_stream
});
let serverbound_stream =
serverbound_stream.insert(TcpStream::connect(listener_addr).unwrap());
// 3. shutdown read during blocking read
scope.spawn(|| {
thread::sleep(Duration::from_secs(1)); // just to be sure
serverbound_stream.shutdown(Shutdown::Read).unwrap();
});
// 2. blocking read
let count = (&*serverbound_stream).read(&mut [0]).unwrap();
assert_eq!(count, 0);
});
}
I expected to see this happen: The .shutdown(Shutdown::Read) makes the blocking read return immediately with Ok(0).
Instead, this happened: The blocking read does not return, making the program hang indefinitely.
Meta
rustc --version --verbose:
rustc 1.78.0-nightly (381d69953 2024-02-24)
binary: rustc
commit-hash: 381d69953bb7c3390cec0fee200f24529cb6320f
commit-date: 2024-02-24
host: x86_64-unknown-linux-gnu
release: 1.78.0-nightly
LLVM version: 18.1.0
Tested on these platforms:
x86_64-pc-windows-msvcwith Microsoft Windows Server 2019 (GitHub CI)x86_64-pc-windows-gnuwith wine 9.1 on Linux
Related
- https://github.com/rust-lang/rust/pull/38867 disabled the test due to "We don't document this as a guarantee for
shutdown", except that we do and also already did when that PR was opened. - https://github.com/rust-lang/rust/pull/94714 enabled the test again as "works on my machine".
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with library/std/src/net/tcp/tests.rs, especially close_read_wakes_up, and run the supplied reproduction on a Windows target to confirm the blocked-read behavior. Trace the TcpStream shutdown and blocking-read paths used there. Done means the test no longer races or hangs on Windows and Shutdown::Read returns the documented Ok(0).
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- networking, operating-systems
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100