| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 13aa41f commit c144799
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -4,6 +4,7 @@ | |||
| 4 | 4 | ||
| 5 | 5 | from . import process | |
| 6 | 6 | from . import reduction | |
| 7 | + from . import util | ||
| 7 | 8 | ||
| 8 | 9 | __all__ = () | |
| 9 | 10 | ||
@@ -333,7 +334,12 @@ def _check_available(self): | |||
| 333 | 334 | # bpo-33725: running arbitrary code after fork() is no longer reliable | |
| 334 | 335 | # on macOS since macOS 10.14 (Mojave). Use spawn by default instead. | |
| 335 | 336 | # gh-84559: We changed everyones default to a thread safeish one in 3.14. | |
| 336 | - if reduction.HAVE_SEND_HANDLE and sys.platform != 'darwin': | ||
| 337 | + if ( | ||
| 338 | + reduction.HAVE_SEND_HANDLE | ||
| 339 | + and sys.platform != 'darwin' | ||
| 340 | + # gh-155717: forkserver requires to write temporary files | ||
| 341 | + and util._has_writeable_tempdir() | ||
| 342 | + ): | ||
| 337 | 343 | _default_context = DefaultContext(_concrete_contexts['forkserver']) | |
| 338 | 344 | else: | |
| 339 | 345 | _default_context = DefaultContext(_concrete_contexts['spawn']) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -10,6 +10,7 @@ | |||
| 10 | 10 | import os | |
| 11 | 11 | import itertools | |
| 12 | 12 | import sys | |
| 13 | + import tempfile | ||
| 13 | 14 | import weakref | |
| 14 | 15 | import atexit | |
| 15 | 16 | import threading # we want threading to install it's | |
@@ -143,6 +144,7 @@ def is_abstract_socket_namespace(address): | |||
| 143 | 144 | # On Windows platforms, we do not create AF_UNIX sockets. | |
| 144 | 145 | _SUN_PATH_MAX = None if os.name == 'nt' else 92 | |
| 145 | 146 | ||
| 147 | + | ||
| 146 | 148 | def _remove_temp_dir(rmtree, tempdir): | |
| 147 | 149 | rmtree(tempdir) | |
| 148 | 150 | ||
@@ -152,7 +154,8 @@ def _remove_temp_dir(rmtree, tempdir): | |||
| 152 | 154 | if current_process is not None: | |
| 153 | 155 | current_process._config['tempdir'] = None | |
| 154 | 156 | ||
| 155 | - def _get_base_temp_dir(tempfile): | ||
| 157 | + | ||
| 158 | + def _get_base_temp_dir(): | ||
| 156 | 159 | """Get a temporary directory where socket files will be created. | |
| 157 | 160 | ||
| 158 | 161 | To prevent additional imports, pass a pre-imported 'tempfile' module. | |
@@ -208,12 +211,13 @@ def _get_base_temp_dir(tempfile): | |||
| 208 | 211 | assert len(base_system_tempdir) + 14 + 14 < _SUN_PATH_MAX | |
| 209 | 212 | return base_system_tempdir | |
| 210 | 213 | ||
| 214 | + | ||
| 211 | 215 | def get_temp_dir(): | |
| 212 | 216 | # get name of a temp directory which will be automatically cleaned up | |
| 213 | 217 | tempdir = process.current_process()._config.get('tempdir') | |
| 214 | 218 | if tempdir is None: | |
| 215 | - import shutil, tempfile | ||
| 216 | - base_tempdir = _get_base_temp_dir(tempfile) | ||
| 219 | + import shutil | ||
| 220 | + base_tempdir = _get_base_temp_dir() | ||
| 217 | 221 | tempdir = tempfile.mkdtemp(prefix='pymp-', dir=base_tempdir) | |
| 218 | 222 | info('created temp directory %s', tempdir) | |
| 219 | 223 | # keep a strong reference to shutil.rmtree(), since the finalizer | |
@@ -223,6 +227,27 @@ def get_temp_dir(): | |||
| 223 | 227 | process.current_process()._config['tempdir'] = tempdir | |
| 224 | 228 | return tempdir | |
| 225 | 229 | ||
| 230 | + | ||
| 231 | + def _has_writeable_tempdir(): | ||
| 232 | + # 'forkserver' requires writeable temporary files. This function is | ||
| 233 | + # called to determine the default context's start method. | ||
| 234 | + # | ||
| 235 | + # See: https://github.com/python/cpython/issues/155717. | ||
| 236 | + | ||
| 237 | + path = _get_base_temp_dir() | ||
| 238 | + if path is None: | ||
| 239 | + return False | ||
| 240 | + | ||
| 241 | + # os.access() is advisory and racy. It can lie on read-only filesystems, | ||
| 242 | + # NFS/network mounts, containers, and immutable-flag files, so we simply | ||
| 243 | + # try to create a file to check if this works and delete it otherwise. | ||
| 244 | + try: | ||
| 245 | + with tempfile.NamedTemporaryFile(dir=path): | ||
| 246 | + return True | ||
| 247 | + except OSError: | ||
| 248 | + return False | ||
| 249 | + | ||
| 250 | + | ||
| 226 | 251 | # | |
| 227 | 252 | # Support for reinitialization of objects when bootstrapping a child process | |
| 228 | 253 | # | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -26,6 +26,7 @@ | |||
| 26 | 26 | import struct | |
| 27 | 27 | import tempfile | |
| 28 | 28 | import operator | |
| 29 | + import pathlib | ||
| 29 | 30 | import pickle | |
| 30 | 31 | import weakref | |
| 31 | 32 | import warnings | |
@@ -6355,6 +6356,40 @@ def test_nested_startmethod(self): | |||
| 6355 | 6356 | # there is no synchronization in the test. | |
| 6356 | 6357 | self.assertSetEqual(set(results), set([2, 1])) | |
| 6357 | 6358 | ||
| 6359 | + @unittest.skipIf(os.name == "nt", "requires POSIX") | ||
| 6360 | + @support.subTests("mode", [ | ||
| 6361 | + os.R_OK, # read-only directory | ||
| 6362 | + os.R_OK | os.X_OK, # read-only directory | ||
| 6363 | + os.W_OK # write-only directory _without_ permissions for creating files | ||
| 6364 | + ]) | ||
| 6365 | + def test_forkserver_requires_writeable_tempdir(self, mode): | ||
| 6366 | + # Regression test to ensure that the defualt start method is | ||
| 6367 | + # not 'forkserver' when the temporary directory is not writeable. | ||
| 6368 | + # | ||
| 6369 | + # See https://github.com/python/cpython/issues/155717. | ||
| 6370 | + | ||
| 6371 | + cmd = '''if 1: | ||
| 6372 | + import os, tempfile | ||
| 6373 | + # We fake the read-onlyiness of /tmp (which is a fallback when | ||
| 6374 | + # the user-defined TMPDIR is not acceptable) by hardcoding the | ||
| 6375 | + # temporary directory for this specific test. | ||
| 6376 | + tempfile.tempdir = os.environ["TMPDIR"] | ||
| 6377 | + | ||
| 6378 | + # Imported after patching 'tempfile' so that the default start | ||
| 6379 | + # method is deduced according to the permissions of TMPDIR. | ||
| 6380 | + import multiprocessing | ||
| 6381 | + if __name__ == "__main__": | ||
| 6382 | + print(multiprocessing.get_start_method()) | ||
| 6383 | + ''' | ||
| 6384 | + | ||
| 6385 | + with support.os_helper.temp_dir() as root: | ||
| 6386 | + TMPDIR = pathlib.Path(root, "TMPDIR") | ||
| 6387 | + TMPDIR.mkdir(mode=mode) | ||
| 6388 | + file = pathlib.Path(TMPDIR, "file") | ||
| 6389 | + self.assertRaises(OSError, file.touch) | ||
| 6390 | + _, out, err = script_helper.assert_python_ok('-c', cmd, TMPDIR=TMPDIR) | ||
| 6391 | + self.assertEqual(out.decode().strip(), "spawn") | ||
| 6392 | + | ||
| 6358 | 6393 | ||
| 6359 | 6394 | @unittest.skipIf(sys.platform == "win32", | |
| 6360 | 6395 | "test semantics don't make sense on Windows") | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,3 @@ | |||
| 1 | + :mod:`multiprocessing`'s default start method on systems with non-writeable | ||
| 2 | + tempfile filesystem is now :ref:`"spawn" <multiprocessing-start-methods>` | ||
| 3 | + instead of ``"forkserver"``. Patch by Bénédikt Tran. | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments