From e5c867ae6983491ee7bbbe8c11016c98342eae69 Mon Sep 17 00:00:00 2001 From: ebonnal Date: Fri, 18 Oct 2024 14:45:50 +0100 Subject: [PATCH] allow timeout + buffersize --- Doc/library/concurrent.futures.rst | 3 +-- Lib/concurrent/futures/_base.py | 5 +---- Lib/concurrent/futures/process.py | 2 +- Lib/test/test_concurrent_futures/executor.py | 10 ++++++---- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/Doc/library/concurrent.futures.rst b/Doc/library/concurrent.futures.rst index 76d37f5a97810e..2f07e64b0cead5 100644 --- a/Doc/library/concurrent.futures.rst +++ b/Doc/library/concurrent.futures.rst @@ -54,8 +54,7 @@ Executor Objects if :meth:`~iterator.__next__` is called and the result isn't available after *timeout* seconds from the original call to :meth:`Executor.map`. *timeout* can be an int or a float. If *timeout* is not specified or - ``None``, there is no limit to the wait time. Incompatible with - *buffersize*. + ``None``, there is no limit to the wait time. If a *fn* call raises an exception, then that exception will be raised when its value is retrieved from the iterator. diff --git a/Lib/concurrent/futures/_base.py b/Lib/concurrent/futures/_base.py index c3797eec1349ed..82dbc7644a1a37 100644 --- a/Lib/concurrent/futures/_base.py +++ b/Lib/concurrent/futures/_base.py @@ -581,7 +581,7 @@ def map(self, fn, *iterables, timeout=None, chunksize=1, buffersize=None): fn: A callable that will take as many arguments as there are passed iterables. timeout: The maximum number of seconds to wait. If None, then there - is no limit on the wait time. Incompatible with buffersize. + is no limit on the wait time. chunksize: The size of the chunks the iterable will be broken into before being passed to a child process. This argument is only used by ProcessPoolExecutor; it is ignored by @@ -603,9 +603,6 @@ def map(self, fn, *iterables, timeout=None, chunksize=1, buffersize=None): if buffersize is not None and buffersize < 1: raise ValueError("buffersize must be None or >= 1.") - if buffersize is not None and timeout is not None: - raise ValueError("cannot specify both buffersize and timeout.") - if timeout is not None: end_time = timeout + time.monotonic() diff --git a/Lib/concurrent/futures/process.py b/Lib/concurrent/futures/process.py index 6b36e27ef4e874..98931ae7c51026 100644 --- a/Lib/concurrent/futures/process.py +++ b/Lib/concurrent/futures/process.py @@ -820,7 +820,7 @@ def map(self, fn, *iterables, timeout=None, chunksize=1, buffersize=None): fn: A callable that will take as many arguments as there are passed iterables. timeout: The maximum number of seconds to wait. If None, then there - is no limit on the wait time. Incompatible with buffersize. + is no limit on the wait time. chunksize: If greater than one, the iterables will be chopped into chunks of size chunksize and submitted to the process pool. If set to one, the items in the list will be sent one at a time. diff --git a/Lib/test/test_concurrent_futures/executor.py b/Lib/test/test_concurrent_futures/executor.py index c89e6a06a5283f..7ee54b22c9aa53 100644 --- a/Lib/test/test_concurrent_futures/executor.py +++ b/Lib/test/test_concurrent_futures/executor.py @@ -74,10 +74,6 @@ def test_map_timeout(self): def test_map_with_buffersize(self): with self.assertRaisesRegex(ValueError, "buffersize must be None or >= 1."): self.executor.map(bool, [], buffersize=0) - with self.assertRaisesRegex( - ValueError, "cannot specify both buffersize and timeout." - ): - self.executor.map(bool, [], timeout=1, buffersize=1) it = range(4) self.assertEqual( @@ -85,6 +81,12 @@ def test_map_with_buffersize(self): list(map(str, it)), ) + def test_map_with_buffersize_and_timeout(self): + it = self.executor.map(time.sleep, (0, 1), timeout=0.5) + next(it) + with self.assertRaises(TimeoutError): + next(it) + def test_map_with_buffersize_on_infinite_iterable(self): results = self.executor.map(str, itertools.count(1), buffersize=1) self.assertEqual(next(iter(results)), "1")