https://github.com/python/cpython/commit/6f1e0c4f01a6f3605d3ec9afa2d38bd65cdb0eb9 commit: 6f1e0c4f01a6f3605d3ec9afa2d38bd65cdb0eb9 branch: 3.13 author: Miss Islington (bot) <[email protected]> committer: serhiy-storchaka <[email protected]> date: 2026-02-24T17:20:53Z summary:
[3.13] gh-137335: Fix unlikely name conflicts for named pipes in multiprocessing and asyncio on Windows (GH-137389) (GH-145171) Since os.stat() raises an OSError for existing named pipe "\\.\pipe\...", os.path.exists() always returns False for it, and tempfile.mktemp() can return a name that matches an existing named pipe. So, tempfile.mktemp() cannot be used to generate unique names for named pipes. Instead, CreateNamedPipe() should be called in a loop with different names until it completes successfully. (cherry picked from commit d6a71f4690c702892644b1fbae90ae9ef733a8ab) Co-authored-by: Serhiy Storchaka <[email protected]> files: A Misc/NEWS.d/next/Library/2025-08-04-23-20-43.gh-issue-137335.IIjDJN.rst M Lib/asyncio/windows_utils.py M Lib/multiprocessing/connection.py diff --git a/Lib/asyncio/windows_utils.py b/Lib/asyncio/windows_utils.py index ef277fac3e291c..acd49441131b04 100644 --- a/Lib/asyncio/windows_utils.py +++ b/Lib/asyncio/windows_utils.py @@ -10,7 +10,6 @@ import msvcrt import os import subprocess -import tempfile import warnings @@ -24,6 +23,7 @@ PIPE = subprocess.PIPE STDOUT = subprocess.STDOUT _mmap_counter = itertools.count() +_MAX_PIPE_ATTEMPTS = 20 # Replacement for os.pipe() using handles instead of fds @@ -31,10 +31,6 @@ def pipe(*, duplex=False, overlapped=(True, True), bufsize=BUFSIZE): """Like os.pipe() but with overlapped support and using handles not fds.""" - address = tempfile.mktemp( - prefix=r'\\.\pipe\python-pipe-{:d}-{:d}-'.format( - os.getpid(), next(_mmap_counter))) - if duplex: openmode = _winapi.PIPE_ACCESS_DUPLEX access = _winapi.GENERIC_READ | _winapi.GENERIC_WRITE @@ -56,9 +52,20 @@ def pipe(*, duplex=False, overlapped=(True, True), bufsize=BUFSIZE): h1 = h2 = None try: - h1 = _winapi.CreateNamedPipe( - address, openmode, _winapi.PIPE_WAIT, - 1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER, _winapi.NULL) + for attempts in itertools.count(): + address = r'\\.\pipe\python-pipe-{:d}-{:d}-{}'.format( + os.getpid(), next(_mmap_counter), os.urandom(8).hex()) + try: + h1 = _winapi.CreateNamedPipe( + address, openmode, _winapi.PIPE_WAIT, + 1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER, _winapi.NULL) + break + except OSError as e: + if attempts >= _MAX_PIPE_ATTEMPTS: + raise + if e.winerror not in (_winapi.ERROR_PIPE_BUSY, + _winapi.ERROR_ACCESS_DENIED): + raise h2 = _winapi.CreateFile( address, access, 0, _winapi.NULL, _winapi.OPEN_EXISTING, diff --git a/Lib/multiprocessing/connection.py b/Lib/multiprocessing/connection.py index abd88adf76e700..efb9ea95ab4696 100644 --- a/Lib/multiprocessing/connection.py +++ b/Lib/multiprocessing/connection.py @@ -44,6 +44,7 @@ CONNECTION_TIMEOUT = 20. _mmap_counter = itertools.count() +_MAX_PIPE_ATTEMPTS = 100 default_family = 'AF_INET' families = ['AF_INET'] @@ -76,8 +77,8 @@ def arbitrary_address(family): elif family == 'AF_UNIX': return tempfile.mktemp(prefix='sock-', dir=util.get_temp_dir()) elif family == 'AF_PIPE': - return tempfile.mktemp(prefix=r'\\.\pipe\pyc-%d-%d-' % - (os.getpid(), next(_mmap_counter)), dir="") + return (r'\\.\pipe\pyc-%d-%d-%s' % + (os.getpid(), next(_mmap_counter), os.urandom(8).hex())) else: raise ValueError('unrecognized family') @@ -455,17 +456,29 @@ class Listener(object): def __init__(self, address=None, family=None, backlog=1, authkey=None): family = family or (address and address_type(address)) \ or default_family - address = address or arbitrary_address(family) - _validate_family(family) + if authkey is not None and not isinstance(authkey, bytes): + raise TypeError('authkey should be a byte string') + if family == 'AF_PIPE': - self._listener = PipeListener(address, backlog) + if address: + self._listener = PipeListener(address, backlog) + else: + for attempts in itertools.count(): + address = arbitrary_address(family) + try: + self._listener = PipeListener(address, backlog) + break + except OSError as e: + if attempts >= _MAX_PIPE_ATTEMPTS: + raise + if e.winerror not in (_winapi.ERROR_PIPE_BUSY, + _winapi.ERROR_ACCESS_DENIED): + raise else: + address = address or arbitrary_address(family) self._listener = SocketListener(address, family, backlog) - if authkey is not None and not isinstance(authkey, bytes): - raise TypeError('authkey should be a byte string') - self._authkey = authkey def accept(self): @@ -553,7 +566,6 @@ def Pipe(duplex=True): ''' Returns pair of connection objects at either end of a pipe ''' - address = arbitrary_address('AF_PIPE') if duplex: openmode = _winapi.PIPE_ACCESS_DUPLEX access = _winapi.GENERIC_READ | _winapi.GENERIC_WRITE @@ -563,15 +575,25 @@ def Pipe(duplex=True): access = _winapi.GENERIC_WRITE obsize, ibsize = 0, BUFSIZE - h1 = _winapi.CreateNamedPipe( - address, openmode | _winapi.FILE_FLAG_OVERLAPPED | - _winapi.FILE_FLAG_FIRST_PIPE_INSTANCE, - _winapi.PIPE_TYPE_MESSAGE | _winapi.PIPE_READMODE_MESSAGE | - _winapi.PIPE_WAIT, - 1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER, - # default security descriptor: the handle cannot be inherited - _winapi.NULL - ) + for attempts in itertools.count(): + address = arbitrary_address('AF_PIPE') + try: + h1 = _winapi.CreateNamedPipe( + address, openmode | _winapi.FILE_FLAG_OVERLAPPED | + _winapi.FILE_FLAG_FIRST_PIPE_INSTANCE, + _winapi.PIPE_TYPE_MESSAGE | _winapi.PIPE_READMODE_MESSAGE | + _winapi.PIPE_WAIT, + 1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER, + # default security descriptor: the handle cannot be inherited + _winapi.NULL + ) + break + except OSError as e: + if attempts >= _MAX_PIPE_ATTEMPTS: + raise + if e.winerror not in (_winapi.ERROR_PIPE_BUSY, + _winapi.ERROR_ACCESS_DENIED): + raise h2 = _winapi.CreateFile( address, access, 0, _winapi.NULL, _winapi.OPEN_EXISTING, _winapi.FILE_FLAG_OVERLAPPED, _winapi.NULL diff --git a/Misc/NEWS.d/next/Library/2025-08-04-23-20-43.gh-issue-137335.IIjDJN.rst b/Misc/NEWS.d/next/Library/2025-08-04-23-20-43.gh-issue-137335.IIjDJN.rst new file mode 100644 index 00000000000000..2311ace10e411d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-08-04-23-20-43.gh-issue-137335.IIjDJN.rst @@ -0,0 +1,2 @@ +Get rid of any possibility of a name conflict for named pipes in +:mod:`multiprocessing` and :mod:`asyncio` on Windows, no matter how small. _______________________________________________ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3//lists/python-checkins.python.org Member address: [email protected]
