]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-26903: Limit ProcessPoolExecutor to 61 workers on Windows (GH-13132) (GH-13643)
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Wed, 29 May 2019 03:12:30 +0000 (20:12 -0700)
committerNed Deily <nad@python.org>
Wed, 29 May 2019 03:12:30 +0000 (23:12 -0400)
Co-Authored-By: brianquinlan <brian@sweetapp.com>
(cherry picked from commit 39889864c09741909da4ec489459d0197ea8f1fc)

Co-authored-by: Brian Quinlan <brian@sweetapp.com>
Doc/library/concurrent.futures.rst
Lib/concurrent/futures/process.py
Lib/test/test_concurrent_futures.py
Misc/NEWS.d/next/Library/2019-05-06-19-17-04.bpo-26903.4payXb.rst [new file with mode: 0644]

index a57491543bf4a4cabe05da7b40fab7002e95d110..24d684a0123d0b363119584368c4f3300efa3dc2 100644 (file)
@@ -216,6 +216,10 @@ to a :class:`ProcessPoolExecutor` will result in deadlock.
    given, it will default to the number of processors on the machine.
    If *max_workers* is lower or equal to ``0``, then a :exc:`ValueError`
    will be raised.
+   On Windows, *max_workers* must be equal or lower than ``61``. If it is not
+   then :exc:`ValueError` will be raised. If *max_workers* is ``None``, then
+   the default chosen will be at most ``61``, even if more processors are
+   available.
    *mp_context* can be a multiprocessing context or None. It will be used to
    launch the workers. If *mp_context* is ``None`` or not given, the default
    multiprocessing context is used.
index 8a0ed98b3e88cade8a1eeb39407357b64b4370b8..6c6905380eff2eba75ac246e5b3869dce526ec08 100644 (file)
@@ -57,6 +57,7 @@ import threading
 import weakref
 from functools import partial
 import itertools
+import sys
 import traceback
 
 # Workers are created as daemon threads and processes. This is done to allow the
@@ -109,6 +110,12 @@ def _python_exit():
 EXTRA_QUEUED_CALLS = 1
 
 
+# On Windows, WaitForMultipleObjects is used to wait for processes to finish.
+# It can wait on, at most, 63 objects. There is an overhead of two objects:
+# - the result queue reader
+# - the thread wakeup reader
+_MAX_WINDOWS_WORKERS = 63 - 2
+
 # Hack to embed stringification of remote traceback in local traceback
 
 class _RemoteTraceback(Exception):
@@ -504,9 +511,16 @@ class ProcessPoolExecutor(_base.Executor):
 
         if max_workers is None:
             self._max_workers = os.cpu_count() or 1
+            if sys.platform == 'win32':
+                self._max_workers = min(_MAX_WINDOWS_WORKERS,
+                                        self._max_workers)
         else:
             if max_workers <= 0:
                 raise ValueError("max_workers must be greater than 0")
+            elif (sys.platform == 'win32' and
+                max_workers > _MAX_WINDOWS_WORKERS):
+                raise ValueError(
+                    f"max_workers must be <= {_MAX_WINDOWS_WORKERS}")
 
             self._max_workers = max_workers
 
index add2bfdd5abe2cdc56899bcbc1dcf32c065c4a1a..ad68909161c7300ab6ef70ca5ed6728095f79700 100644 (file)
@@ -754,6 +754,13 @@ class ThreadPoolExecutorTest(ThreadPoolMixin, ExecutorTest, BaseTestCase):
 
 
 class ProcessPoolExecutorTest(ExecutorTest):
+
+    @unittest.skipUnless(sys.platform=='win32', 'Windows-only process limit')
+    def test_max_workers_too_large(self):
+        with self.assertRaisesRegex(ValueError,
+                                    "max_workers must be <= 61"):
+            futures.ProcessPoolExecutor(max_workers=62)
+
     def test_killed_child(self):
         # When a child process is abruptly terminated, the whole pool gets
         # "broken".
diff --git a/Misc/NEWS.d/next/Library/2019-05-06-19-17-04.bpo-26903.4payXb.rst b/Misc/NEWS.d/next/Library/2019-05-06-19-17-04.bpo-26903.4payXb.rst
new file mode 100644 (file)
index 0000000..ec3aa05
--- /dev/null
@@ -0,0 +1 @@
+Limit `max_workers` in `ProcessPoolExecutor` to 61 to work around a WaitForMultipleObjects limitation.
\ No newline at end of file