Skip to content

Pipe inheritance broken on Windows since 3.13 #133506

Open
@schlamar

Description

@schlamar

Bug report

Bug description:

Passing an inheritable pipe file descriptor to a child process is somehow broken on Windows since Python 3.13. Opening the file descriptor in the child process crashes with [WinError 6] the handle is invalid.

> py -3.12-64 .\main.py
INFO:root:S: Starting
INFO:root:Duration for 10000 requests: 0.37447249999968335 s
INFO:root:S: EOF Quitting
> py -3.13-64 .\main.py
INFO:root:S: Starting
Traceback (most recent call last):
  File "D:\temp\test-pipe-main\sub.py", line 31, in <module>
    main()
    ~~~~^^
  File "D:\temp\test-pipe-main\sub.py", line 14, in main
    reply_write_fobj = os.fdopen(reply_write_fd, "wb", buffering=0)
  File "<frozen os>", line 1068, in fdopen
OSError: [WinError 6] Das Handle ist ungültig

Minimal example

import logging
import os
import pickle
import sys
import time


def main():
    logging.basicConfig(level=logging.INFO)
    reply_read_fd, reply_write_fd = os.pipe()
    request_read_fd, request_write_fd = os.pipe()
    child_fds = [reply_write_fd, request_read_fd]
    for fd in child_fds:
        os.set_inheritable(fd, True)

    reply_read_fobj = os.fdopen(reply_read_fd, "rb", buffering=0)
    request_write_fobj = os.fdopen(request_write_fd, "wb", buffering=0)

    child_pid = os.spawnl(
        os.P_NOWAIT,
        sys.executable,
        os.path.basename(sys.executable),
        "sub.py",
        *(str(fd) for fd in child_fds),
    )
    for fd in child_fds:
        os.close(fd)

    number_requests = 10000
    start = time.perf_counter()
    for i in range(number_requests):
        request = f"test{i}"
        pickle.dump(request, request_write_fobj)
        logging.debug(f"C: Sent {request}")
        reply = pickle.load(reply_read_fobj)
        logging.debug(f"C: Got {reply}")

    duration = time.perf_counter() - start
    logging.info(f"Duration for {number_requests} requests: {duration} s")
    request_write_fobj.close()
    os.waitpid(child_pid, 0)
    time.sleep(1)


if __name__ == "__main__":
    main()
import logging
import os
import pickle
import sys


def main():
    logging.basicConfig(level=logging.INFO)
    reply_write_fd = int(sys.argv[1])
    request_read_fd = int(sys.argv[2])

    logging.info("S: Starting")

    reply_write_fobj = os.fdopen(reply_write_fd, "wb", buffering=0)
    request_read_fobj = os.fdopen(request_read_fd, "rb", buffering=0)
    logging.debug("S: Sent event")
    while True:
        try:
            request: str = pickle.load(request_read_fobj)
        except EOFError:
            logging.info("S: EOF Quitting")
            return
        logging.debug(f"S: Handle {request}")
        reply = "test"
        pickle.dump(reply, reply_write_fobj)

        logging.debug("S: Sent event")


if __name__ == "__main__":
    main()

CPython versions tested on:

3.12, 3.13

Operating systems tested on:

Windows

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.13bugs and security fixes3.14bugs and security fixesOS-windowstype-bugAn unexpected behavior, bug, or error

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions