From 77c28e308c276315df332dbaf542b717940f4536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sat, 9 May 2026 12:58:41 +0200 Subject: [PATCH 1/2] gh-149527: fix creation of temporary socket files post GH-148578 --- Lib/multiprocessing/connection.py | 9 ++-- Lib/multiprocessing/util.py | 52 ++++++++++++++----- ...-05-09-12-58-04.gh-issue-149527.hX36Yh.rst | 3 ++ 3 files changed, 46 insertions(+), 18 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-05-09-12-58-04.gh-issue-149527.hX36Yh.rst diff --git a/Lib/multiprocessing/connection.py b/Lib/multiprocessing/connection.py index e37ec07d722ca8..15f6590288c9ab 100644 --- a/Lib/multiprocessing/connection.py +++ b/Lib/multiprocessing/connection.py @@ -76,11 +76,12 @@ def arbitrary_address(family): if family == 'AF_INET': return ('localhost', 0) elif family == 'AF_UNIX': - # NOTE: util.get_temp_dir() is a 0o700 per-process directory. A - # mktemp-style ToC vs ToU concern is not important; bind() surfaces - # the extremely unlikely collision as EADDRINUSE. + # NOTE: util.get_temp_dir() is a 0o700 per-process directory. + # A mktemp-style ToC vs ToU concern is not important as bind() + # surfaces the extremely unlikely collision as EADDRINUSE. + suffix = os.urandom(util._TMPSOCK_SUFFIXLEN // 2).hex() return os.path.join(util.get_temp_dir(), - f'sock-{os.urandom(6).hex()}') + f"{util._TMPSOCK_PREFIX}{suffix}") elif family == 'AF_PIPE': return (r'\\.\pipe\pyc-%d-%d-%s' % (os.getpid(), next(_mmap_counter), os.urandom(8).hex())) diff --git a/Lib/multiprocessing/util.py b/Lib/multiprocessing/util.py index 549fb07c27549e..3ddaf8ac60cc39 100644 --- a/Lib/multiprocessing/util.py +++ b/Lib/multiprocessing/util.py @@ -143,6 +143,38 @@ def is_abstract_socket_namespace(address): # On Windows platforms, we do not create AF_UNIX sockets. _SUN_PATH_MAX = None if os.name == 'nt' else 92 +# The temporary socket file path (without NULL terminator) is +# +# TEMPDIR/{_TMPPYMP_PREFIX}{S1}/{_TMPSOCK_PREFIX}{S2} +# +# where +# +# - S1 is a random suffix of length 8 generated by get_temp_dir(). +# This is used to generate the temporary sub-directory that will +# contains the socket file. Update _TMPPYMP_SUFFIXLEN accordingly +# if the random suffix length chosen by tempfile.mkdtemp() changes. +# +# - S2 is a random suffix of length 2N generated by os.urandom(N).hex() +# in multiprocessing.connection.arbitrary_address(). The value of N +# is not fixed and may vary across Python versions as to minimize +# collisions and path lengths. +# +_TMPPYMP_PREFIX = "pymp-" +_TMPPYMP_PREFIXLEN = len(_TMPPYMP_PREFIX) +_TMPPYMP_SUFFIXLEN = 8 +_TMPSOCK_PREFIX = "sock-" +_TMPSOCK_PREFIXLEN = len(_TMPSOCK_PREFIX) +_TMPSOCK_SUFFIXLEN = 12 + +# Length of the socket filepath from the chosen temporary directory, +# including a leading path separator and the path separator between +# the temporary subdirectory and the socket filename. +_SUN_PATH_LEN_RESERVED = ( + len(os.path) + _TMPPYMP_PREFIXLEN + _TMPPYMP_SUFFIXLEN + + len(os.path) + _TMPSOCK_PREFIXLEN + _TMPSOCK_SUFFIXLEN +) + + def _remove_temp_dir(rmtree, tempdir): rmtree(tempdir) @@ -159,9 +191,8 @@ def _get_base_temp_dir(tempfile): """ if os.name == 'nt': return None - # Most of the time, the default temporary directory is /tmp. Thus, - # listener sockets files "$TMPDIR/pymp-XXXXXXXX/sock-XXXXXXXX" do - # not have a path length exceeding SUN_PATH_MAX. + # Most of the time, the default temporary directory is /tmp. As such, + # the path length of listener sockets files does not exceed SUN_PATH_MAX. # # If users specify their own temporary directory, we may be unable # to create those files. Therefore, we fall back to the system-wide @@ -169,14 +200,7 @@ def _get_base_temp_dir(tempfile): # # See https://github.com/python/cpython/issues/132124. base_tempdir = tempfile.gettempdir() - # Files created in a temporary directory are suffixed by a string - # generated by tempfile._RandomNameSequence, which, by design, - # is 8 characters long. - # - # Thus, the socket file path length (without NULL terminator) will be: - # - # len(base_tempdir + '/pymp-XXXXXXXX' + '/sock-XXXXXXXX') - sun_path_len = len(base_tempdir) + 14 + 14 + sun_path_len = len(base_tempdir) + _SUN_PATH_LEN_RESERVED # Strict inequality to account for the NULL terminator. # See https://github.com/python/cpython/issues/140734. if sun_path_len < _SUN_PATH_MAX: @@ -204,8 +228,8 @@ def _get_base_temp_dir(tempfile): # not be able to write socket files out there. return base_tempdir warn("Ignoring user-defined temporary directory: %s", base_tempdir) - # at most max(map(len, dirlist)) + 14 + 14 = 36 characters - assert len(base_system_tempdir) + 14 + 14 < _SUN_PATH_MAX + # The following assertion must be satisfied with the chosen constants. + assert len(base_system_tempdir) + _SUN_PATH_LEN_RESERVED < _SUN_PATH_MAX return base_system_tempdir def get_temp_dir(): @@ -214,7 +238,7 @@ def get_temp_dir(): if tempdir is None: import shutil, tempfile base_tempdir = _get_base_temp_dir(tempfile) - tempdir = tempfile.mkdtemp(prefix='pymp-', dir=base_tempdir) + tempdir = tempfile.mkdtemp(prefix=_TMPPYMP_PREFIX, dir=base_tempdir) info('created temp directory %s', tempdir) # keep a strong reference to shutil.rmtree(), since the finalizer # can be called late during Python shutdown diff --git a/Misc/NEWS.d/next/Library/2026-05-09-12-58-04.gh-issue-149527.hX36Yh.rst b/Misc/NEWS.d/next/Library/2026-05-09-12-58-04.gh-issue-149527.hX36Yh.rst new file mode 100644 index 00000000000000..d75b285554c0fa --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-05-09-12-58-04.gh-issue-149527.hX36Yh.rst @@ -0,0 +1,3 @@ +:mod:`multiprocessing`: fix creation of temporary socket files following +:gh:`137335` (`PR#148578 `__ +and corresponding backports). Patch by Bénédikt Tran. From ab905ba645b010815f2d3fe7e053461dd2e4a39b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sat, 9 May 2026 13:35:41 +0200 Subject: [PATCH 2/2] Update Lib/multiprocessing/util.py --- Lib/multiprocessing/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/multiprocessing/util.py b/Lib/multiprocessing/util.py index 3ddaf8ac60cc39..b5bad1f6c97f66 100644 --- a/Lib/multiprocessing/util.py +++ b/Lib/multiprocessing/util.py @@ -170,8 +170,8 @@ def is_abstract_socket_namespace(address): # including a leading path separator and the path separator between # the temporary subdirectory and the socket filename. _SUN_PATH_LEN_RESERVED = ( - len(os.path) + _TMPPYMP_PREFIXLEN + _TMPPYMP_SUFFIXLEN + - len(os.path) + _TMPSOCK_PREFIXLEN + _TMPSOCK_SUFFIXLEN + len(os.path.sep) + _TMPPYMP_PREFIXLEN + _TMPPYMP_SUFFIXLEN + + len(os.path.sep) + _TMPSOCK_PREFIXLEN + _TMPSOCK_SUFFIXLEN )