Skip to content

Commit 2a0a5e9

Browse files
gh-137335: Fix unlikely nmae conflicts for named pipes in multiprocessing and asyncio on Windows
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.
1 parent c5cebe1 commit 2a0a5e9

File tree

3 files changed

+49
-30
lines changed

3 files changed

+49
-30
lines changed

Lib/asyncio/windows_utils.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
raise ImportError('win32 only')
77

88
import _winapi
9-
import itertools
109
import msvcrt
1110
import os
1211
import subprocess
@@ -23,18 +22,13 @@
2322
BUFSIZE = 8192
2423
PIPE = subprocess.PIPE
2524
STDOUT = subprocess.STDOUT
26-
_mmap_counter = itertools.count()
2725

2826

2927
# Replacement for os.pipe() using handles instead of fds
3028

3129

3230
def pipe(*, duplex=False, overlapped=(True, True), bufsize=BUFSIZE):
3331
"""Like os.pipe() but with overlapped support and using handles not fds."""
34-
address = tempfile.mktemp(
35-
prefix=r'\\.\pipe\python-pipe-{:d}-{:d}-'.format(
36-
os.getpid(), next(_mmap_counter)))
37-
3832
if duplex:
3933
openmode = _winapi.PIPE_ACCESS_DUPLEX
4034
access = _winapi.GENERIC_READ | _winapi.GENERIC_WRITE
@@ -54,11 +48,21 @@ def pipe(*, duplex=False, overlapped=(True, True), bufsize=BUFSIZE):
5448
else:
5549
flags_and_attribs = 0
5650

51+
prefix = fr'\\.\pipe\python-pipe-{os.getpid()}-'
52+
names = tempfile._get_candidate_names()
5753
h1 = h2 = None
5854
try:
59-
h1 = _winapi.CreateNamedPipe(
60-
address, openmode, _winapi.PIPE_WAIT,
61-
1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER, _winapi.NULL)
55+
while True:
56+
address = prefix + next(names)
57+
try:
58+
h1 = _winapi.CreateNamedPipe(
59+
address, openmode, _winapi.PIPE_WAIT,
60+
1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER, _winapi.NULL)
61+
break
62+
except OSError as e:
63+
if e.winerror not in (_winapi.ERROR_PIPE_BUSY,
64+
_winapi.ERROR_ACCESS_DENIED):
65+
raise
6266

6367
h2 = _winapi.CreateFile(
6468
address, access, 0, _winapi.NULL, _winapi.OPEN_EXISTING,

Lib/multiprocessing/connection.py

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
import errno
1313
import io
14-
import itertools
1514
import os
1615
import sys
1716
import socket
@@ -45,8 +44,6 @@
4544
# A very generous timeout when it comes to local connections...
4645
CONNECTION_TIMEOUT = 20.
4746

48-
_mmap_counter = itertools.count()
49-
5047
default_family = 'AF_INET'
5148
families = ['AF_INET']
5249

@@ -78,8 +75,7 @@ def arbitrary_address(family):
7875
elif family == 'AF_UNIX':
7976
return tempfile.mktemp(prefix='sock-', dir=util.get_temp_dir())
8077
elif family == 'AF_PIPE':
81-
return tempfile.mktemp(prefix=r'\\.\pipe\pyc-%d-%d-' %
82-
(os.getpid(), next(_mmap_counter)), dir="")
78+
return fr'\\.\pipe\pyc-{os.getpid()}-{next(tempfile._get_candidate_names())}'
8379
else:
8480
raise ValueError('unrecognized family')
8581

@@ -472,17 +468,27 @@ class Listener(object):
472468
def __init__(self, address=None, family=None, backlog=1, authkey=None):
473469
family = family or (address and address_type(address)) \
474470
or default_family
475-
address = address or arbitrary_address(family)
476-
477471
_validate_family(family)
472+
if authkey is not None and not isinstance(authkey, bytes):
473+
raise TypeError('authkey should be a byte string')
474+
478475
if family == 'AF_PIPE':
479-
self._listener = PipeListener(address, backlog)
476+
if address:
477+
self._listener = PipeListener(address, backlog)
478+
else:
479+
while True:
480+
address = arbitrary_address(family)
481+
try:
482+
self._listener = PipeListener(address, backlog)
483+
break
484+
except OSError as e:
485+
if e.winerror not in (_winapi.ERROR_PIPE_BUSY,
486+
_winapi.ERROR_ACCESS_DENIED):
487+
raise
480488
else:
489+
address = address or arbitrary_address(family)
481490
self._listener = SocketListener(address, family, backlog)
482491

483-
if authkey is not None and not isinstance(authkey, bytes):
484-
raise TypeError('authkey should be a byte string')
485-
486492
self._authkey = authkey
487493

488494
def accept(self):
@@ -570,7 +576,6 @@ def Pipe(duplex=True):
570576
'''
571577
Returns pair of connection objects at either end of a pipe
572578
'''
573-
address = arbitrary_address('AF_PIPE')
574579
if duplex:
575580
openmode = _winapi.PIPE_ACCESS_DUPLEX
576581
access = _winapi.GENERIC_READ | _winapi.GENERIC_WRITE
@@ -580,15 +585,23 @@ def Pipe(duplex=True):
580585
access = _winapi.GENERIC_WRITE
581586
obsize, ibsize = 0, BUFSIZE
582587

583-
h1 = _winapi.CreateNamedPipe(
584-
address, openmode | _winapi.FILE_FLAG_OVERLAPPED |
585-
_winapi.FILE_FLAG_FIRST_PIPE_INSTANCE,
586-
_winapi.PIPE_TYPE_MESSAGE | _winapi.PIPE_READMODE_MESSAGE |
587-
_winapi.PIPE_WAIT,
588-
1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER,
589-
# default security descriptor: the handle cannot be inherited
590-
_winapi.NULL
591-
)
588+
while True:
589+
address = arbitrary_address('AF_PIPE')
590+
try:
591+
h1 = _winapi.CreateNamedPipe(
592+
address, openmode | _winapi.FILE_FLAG_OVERLAPPED |
593+
_winapi.FILE_FLAG_FIRST_PIPE_INSTANCE,
594+
_winapi.PIPE_TYPE_MESSAGE | _winapi.PIPE_READMODE_MESSAGE |
595+
_winapi.PIPE_WAIT,
596+
1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER,
597+
# default security descriptor: the handle cannot be inherited
598+
_winapi.NULL
599+
)
600+
break
601+
except OSError as e:
602+
if e.winerror not in (_winapi.ERROR_PIPE_BUSY,
603+
_winapi.ERROR_ACCESS_DENIED):
604+
raise
592605
h2 = _winapi.CreateFile(
593606
address, access, 0, _winapi.NULL, _winapi.OPEN_EXISTING,
594607
_winapi.FILE_FLAG_OVERLAPPED, _winapi.NULL
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Get rid of any possibility of a name conflict for named pipes in
2+
:mod:`miltiprocessing` and :mod:`asyncio` on Windows, no matter how small.

0 commit comments

Comments
 (0)