Skip to content

windows: TcpStream::shutdown does not wake up blocking reads #121594

Open
@lukas-code

Description

@lukas-code

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:

All currently blocked and future reads will return Ok(0).

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 #121523 (comment) and #120543 (comment). 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-msvc with Microsoft Windows Server 2019 (GitHub CI)
  • x86_64-pc-windows-gnu with wine 9.1 on Linux

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-docsArea: Documentation for any part of the project, including the compiler, standard library, and toolsA-ioArea: `std::io`, `std::fs`, `std::net` and `std::path`C-bugCategory: This is a bug.O-windowsOperating system: WindowsT-libs-apiRelevant to the library API team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions