https://github.com/python/cpython/commit/c1447994a42004a81a8caca026218daf69d451c8
commit: c1447994a42004a81a8caca026218daf69d451c8
branch: main
author: Bénédikt Tran <[email protected]>
committer: picnixz <[email protected]>
date: 2026-08-16T10:23:02+02:00
summary:
gh-155717: use `spawn` as the default start method for read-only filesystems
(#155827)
The "forkserver" start method (the default start method on non-Windows systems)
requires the ability to write temporary files, which is not possible if TMPDIR
is read-only (e.g., k8s containers mounted with `readOnlyRootFilesystem=True`).
On such filesystems, the default start method changes from "forkserver" to
"spawn".
files:
A Misc/NEWS.d/next/Library/2026-08-15-09-47-23.gh-issue-155717.jWFLR2.rst
M Lib/multiprocessing/context.py
M Lib/multiprocessing/util.py
M Lib/test/_test_multiprocessing.py
diff --git a/Lib/multiprocessing/context.py b/Lib/multiprocessing/context.py
index 45c393798deaca2..e94e6c8690bf506 100644
--- a/Lib/multiprocessing/context.py
+++ b/Lib/multiprocessing/context.py
@@ -4,6 +4,7 @@
from . import process
from . import reduction
+from . import util
__all__ = ()
@@ -333,7 +334,12 @@ def _check_available(self):
# bpo-33725: running arbitrary code after fork() is no longer reliable
# on macOS since macOS 10.14 (Mojave). Use spawn by default instead.
# gh-84559: We changed everyones default to a thread safeish one in 3.14.
- if reduction.HAVE_SEND_HANDLE and sys.platform != 'darwin':
+ if (
+ reduction.HAVE_SEND_HANDLE
+ and sys.platform != 'darwin'
+ # gh-155717: forkserver requires to write temporary files
+ and util._has_writeable_tempdir()
+ ):
_default_context = DefaultContext(_concrete_contexts['forkserver'])
else:
_default_context = DefaultContext(_concrete_contexts['spawn'])
diff --git a/Lib/multiprocessing/util.py b/Lib/multiprocessing/util.py
index 549fb07c27549e0..cf7e0b2990598b7 100644
--- a/Lib/multiprocessing/util.py
+++ b/Lib/multiprocessing/util.py
@@ -10,6 +10,7 @@
import os
import itertools
import sys
+import tempfile
import weakref
import atexit
import threading # we want threading to install it's
@@ -143,6 +144,7 @@ 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
+
def _remove_temp_dir(rmtree, tempdir):
rmtree(tempdir)
@@ -152,7 +154,8 @@ def _remove_temp_dir(rmtree, tempdir):
if current_process is not None:
current_process._config['tempdir'] = None
-def _get_base_temp_dir(tempfile):
+
+def _get_base_temp_dir():
"""Get a temporary directory where socket files will be created.
To prevent additional imports, pass a pre-imported 'tempfile' module.
@@ -208,12 +211,13 @@ def _get_base_temp_dir(tempfile):
assert len(base_system_tempdir) + 14 + 14 < _SUN_PATH_MAX
return base_system_tempdir
+
def get_temp_dir():
# get name of a temp directory which will be automatically cleaned up
tempdir = process.current_process()._config.get('tempdir')
if tempdir is None:
- import shutil, tempfile
- base_tempdir = _get_base_temp_dir(tempfile)
+ import shutil
+ base_tempdir = _get_base_temp_dir()
tempdir = tempfile.mkdtemp(prefix='pymp-', dir=base_tempdir)
info('created temp directory %s', tempdir)
# keep a strong reference to shutil.rmtree(), since the finalizer
@@ -223,6 +227,27 @@ def get_temp_dir():
process.current_process()._config['tempdir'] = tempdir
return tempdir
+
+def _has_writeable_tempdir():
+ # 'forkserver' requires writeable temporary files. This function is
+ # called to determine the default context's start method.
+ #
+ # See: https://github.com/python/cpython/issues/155717.
+
+ path = _get_base_temp_dir()
+ if path is None:
+ return False
+
+ # os.access() is advisory and racy. It can lie on read-only filesystems,
+ # NFS/network mounts, containers, and immutable-flag files, so we simply
+ # try to create a file to check if this works and delete it otherwise.
+ try:
+ with tempfile.NamedTemporaryFile(dir=path):
+ return True
+ except OSError:
+ return False
+
+
#
# Support for reinitialization of objects when bootstrapping a child process
#
diff --git a/Lib/test/_test_multiprocessing.py
b/Lib/test/_test_multiprocessing.py
index ba1c0de5d283323..4aaaa22f4274f03 100644
--- a/Lib/test/_test_multiprocessing.py
+++ b/Lib/test/_test_multiprocessing.py
@@ -26,6 +26,7 @@
import struct
import tempfile
import operator
+import pathlib
import pickle
import weakref
import warnings
@@ -6355,6 +6356,40 @@ def test_nested_startmethod(self):
# there is no synchronization in the test.
self.assertSetEqual(set(results), set([2, 1]))
+ @unittest.skipIf(os.name == "nt", "requires POSIX")
+ @support.subTests("mode", [
+ os.R_OK, # read-only directory
+ os.R_OK | os.X_OK, # read-only directory
+ os.W_OK # write-only directory _without_ permissions for creating files
+ ])
+ def test_forkserver_requires_writeable_tempdir(self, mode):
+ # Regression test to ensure that the defualt start method is
+ # not 'forkserver' when the temporary directory is not writeable.
+ #
+ # See https://github.com/python/cpython/issues/155717.
+
+ cmd = '''if 1:
+ import os, tempfile
+ # We fake the read-onlyiness of /tmp (which is a fallback when
+ # the user-defined TMPDIR is not acceptable) by hardcoding the
+ # temporary directory for this specific test.
+ tempfile.tempdir = os.environ["TMPDIR"]
+
+ # Imported after patching 'tempfile' so that the default start
+ # method is deduced according to the permissions of TMPDIR.
+ import multiprocessing
+ if __name__ == "__main__":
+ print(multiprocessing.get_start_method())
+ '''
+
+ with support.os_helper.temp_dir() as root:
+ TMPDIR = pathlib.Path(root, "TMPDIR")
+ TMPDIR.mkdir(mode=mode)
+ file = pathlib.Path(TMPDIR, "file")
+ self.assertRaises(OSError, file.touch)
+ _, out, err = script_helper.assert_python_ok('-c', cmd,
TMPDIR=TMPDIR)
+ self.assertEqual(out.decode().strip(), "spawn")
+
@unittest.skipIf(sys.platform == "win32",
"test semantics don't make sense on Windows")
diff --git
a/Misc/NEWS.d/next/Library/2026-08-15-09-47-23.gh-issue-155717.jWFLR2.rst
b/Misc/NEWS.d/next/Library/2026-08-15-09-47-23.gh-issue-155717.jWFLR2.rst
new file mode 100644
index 000000000000000..0994dc8d03051f4
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-08-15-09-47-23.gh-issue-155717.jWFLR2.rst
@@ -0,0 +1,3 @@
+:mod:`multiprocessing`'s default start method on systems with non-writeable
+tempfile filesystem is now :ref:`"spawn" <multiprocessing-start-methods>`
+instead of ``"forkserver"``. Patch by Bénédikt Tran.
_______________________________________________
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]