https://github.com/python/cpython/commit/d6a71f4690c702892644b1fbae90ae9ef733a8ab
commit: d6a71f4690c702892644b1fbae90ae9ef733a8ab
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-02-24T13:27:49+02:00
summary:
gh-137335: Fix unlikely name conflicts for named pipes in multiprocessing and
asyncio on Windows (#137389)
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.
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 64ec53884aeb5d..41b36066c62fcb 100644
--- a/Lib/multiprocessing/connection.py
+++ b/Lib/multiprocessing/connection.py
@@ -46,6 +46,7 @@
CONNECTION_TIMEOUT = 20.
_mmap_counter = itertools.count()
+_MAX_PIPE_ATTEMPTS = 100
default_family = 'AF_INET'
families = ['AF_INET']
@@ -78,8 +79,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')
@@ -472,17 +473,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):
@@ -570,7 +583,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
@@ -580,15 +592,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]