diff -r dbad4564cd12 Doc/library/concurrent.futures.rst --- a/Doc/library/concurrent.futures.rst Mon Jan 27 12:18:49 2014 +0100 +++ b/Doc/library/concurrent.futures.rst Mon Jan 27 12:45:47 2014 -0500 @@ -338,7 +338,8 @@ Wait for the :class:`Future` instances (possibly created by different :class:`Executor` instances) given by *fs* to complete. Returns a named - 2-tuple of sets. The first set, named ``done``, contains the futures that + 2-tuple of sets. Any futures given by *fs* that are duplicated will be + returned once. The first set, named ``done``, contains the futures that completed (finished or were cancelled) before the wait completed. The second set, named ``not_done``, contains uncompleted futures. diff -r dbad4564cd12 Lib/concurrent/futures/_base.py --- a/Lib/concurrent/futures/_base.py Mon Jan 27 12:18:49 2014 +0100 +++ b/Lib/concurrent/futures/_base.py Mon Jan 27 12:45:47 2014 -0500 @@ -251,12 +251,14 @@ 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. If any given Futures are duplicated, they will be returned + 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 + not_done = fs - done if (return_when == FIRST_COMPLETED) and done: return DoneAndNotDoneFutures(done, not_done) diff -r dbad4564cd12 Lib/test/test_concurrent_futures.py --- a/Lib/test/test_concurrent_futures.py Mon Jan 27 12:18:49 2014 +0100 +++ b/Lib/test/test_concurrent_futures.py Mon Jan 27 12:45:47 2014 -0500 @@ -290,6 +290,20 @@ future1]), finished) self.assertEqual(set([future2]), pending) + def test_duplicate_futures(self): + # Issue #20369: wait() on duplicate finished futures should not timeout + future1 = self.executor.submit(mul, 6, 7) + time.sleep(1) + self.assertEqual(future1._state, FINISHED, "Precondition not valid for test") + start = time.time() + done, notdone = futures.wait([future1,future1], + return_when=futures.ALL_COMPLETED, + timeout=2) + end = time.time() + self.assertEqual( set([future1]), done) + self.assertEqual( len(notdone), 0) + self.assertLess(end-start, 2, "Wait timed out on finished duplicated futures") + class ThreadPoolWaitTests(ThreadPoolMixin, WaitTests, unittest.TestCase):