Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-20369: concurrent.futures.wait() now deduplicates futures given a… #30168

Merged
merged 2 commits into from
Jan 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion Doc/library/concurrent.futures.rst
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,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
Expand Down
13 changes: 7 additions & 6 deletions Lib/concurrent/futures/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,13 +282,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:
Expand All @@ -307,7 +308,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."""
Expand Down
8 changes: 8 additions & 0 deletions Lib/test/test_concurrent_futures.py
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:func:`concurrent.futures.wait` no longer blocks forever when given duplicate Futures. Patch by Kumar Aditya.