diff --git a/Doc/library/concurrent.futures.rst b/Doc/library/concurrent.futures.rst index 897efc2f544..f62b5e35463 100644 --- a/Doc/library/concurrent.futures.rst +++ b/Doc/library/concurrent.futures.rst @@ -435,7 +435,8 @@ Module Functions .. function:: wait(fs, timeout=None, return_when=ALL_COMPLETED) Wait for the :class:`Future` instances (possibly created by different - :class:`Executor` instances) given by *fs* to complete. Returns a named + :class:`Executor` instances) given by *fs* to complete. Duplicate futures + given to *fs* are removed and will be returned only once. Returns a named 2-tuple of sets. The first set, named ``done``, contains the futures that completed (finished or cancelled futures) before the wait completed. The second set, named ``not_done``, contains the futures that did not complete diff --git a/Lib/concurrent/futures/_base.py b/Lib/concurrent/futures/_base.py index 6095026cb27..5c00f2edbe5 100644 --- a/Lib/concurrent/futures/_base.py +++ b/Lib/concurrent/futures/_base.py @@ -284,13 +284,14 @@ def wait(fs, timeout=None, return_when=ALL_COMPLETED): A named 2-tuple of sets. The first set, named 'done', contains the futures that completed (is finished or cancelled) before the wait completed. The second set, named 'not_done', contains uncompleted - futures. + futures. Duplicate futures given to *fs* are removed and will be + returned only once. """ + fs = set(fs) with _AcquireFutures(fs): - done = set(f for f in fs - if f._state in [CANCELLED_AND_NOTIFIED, FINISHED]) - not_done = set(fs) - done - + done = {f for f in fs + if f._state in [CANCELLED_AND_NOTIFIED, FINISHED]} + not_done = fs - done if (return_when == FIRST_COMPLETED) and done: return DoneAndNotDoneFutures(done, not_done) elif (return_when == FIRST_EXCEPTION) and done: @@ -309,7 +310,7 @@ def wait(fs, timeout=None, return_when=ALL_COMPLETED): f._waiters.remove(waiter) done.update(waiter.finished_futures) - return DoneAndNotDoneFutures(done, set(fs) - done) + return DoneAndNotDoneFutures(done, fs - done) class Future(object): """Represents the result of an asynchronous computation.""" diff --git a/Lib/test/test_concurrent_futures.py b/Lib/test/test_concurrent_futures.py index 84209ca2520..29e041deeca 100644 --- a/Lib/test/test_concurrent_futures.py +++ b/Lib/test/test_concurrent_futures.py @@ -579,6 +579,14 @@ def test_shutdown_no_wait(self): class WaitTests: + def test_20369(self): + # See https://bugs.python.org/issue20369 + future = self.executor.submit(time.sleep, 1.5) + done, not_done = futures.wait([future, future], + return_when=futures.ALL_COMPLETED) + self.assertEqual({future}, done) + self.assertEqual(set(), not_done) + def test_first_completed(self): future1 = self.executor.submit(mul, 21, 2) diff --git a/Misc/NEWS.d/next/Library/2021-12-17-12-06-40.bpo-20369.zzLuBz.rst b/Misc/NEWS.d/next/Library/2021-12-17-12-06-40.bpo-20369.zzLuBz.rst new file mode 100644 index 00000000000..cc5cd0067e6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-12-17-12-06-40.bpo-20369.zzLuBz.rst @@ -0,0 +1 @@ +:func:`concurrent.futures.wait` no longer blocks forever when given duplicate Futures. Patch by Kumar Aditya.