Skip to content

Commit 488abe6

Browse files
Update docs and cancel all on timeout.
1 parent a2c0f7e commit 488abe6

5 files changed

Lines changed: 46 additions & 31 deletions

File tree

Doc/library/concurrent.futures.rst

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,18 @@ Executor Objects
5151
The returned iterator raises a :exc:`TimeoutError`
5252
if :meth:`~iterator.__next__` is called and the result isn't available
5353
after *timeout* seconds from the original call to :meth:`Executor.map`.
54-
*timeout* can be an int or a float. If *timeout* is not specified or
54+
*timeout* can be an int or a float.
55+
It cancels all future calls of *fn* and closes the iterator.
56+
If *timeout* is not specified or
5557
``None``, there is no limit to the wait time.
5658

5759
If a *fn* call raises an exception, then that exception will be
5860
raised when its value is retrieved from the iterator.
61+
It does not cancel future calls of *fn*.
62+
63+
The returned iterator has method :meth:`!close` which cancels all
64+
future calls of *fn* and discards the results of already finished calls
65+
if they are available.
5966

6067
When using :class:`ProcessPoolExecutor`, this method chops *iterables*
6168
into a number of chunks which it submits to the pool as separate
@@ -68,6 +75,10 @@ Executor Objects
6875
.. versionchanged:: 3.5
6976
Added the *chunksize* argument.
7077

78+
.. versionchanged:: 3.13
79+
The returned iterator no longer automatically closed if a *fn* call
80+
raises an exception.
81+
7182
.. method:: shutdown(wait=True, *, cancel_futures=False)
7283

7384
Signal the executor that it should free any resources that it is using

Doc/whatsnew/3.13.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,11 @@ Other Language Changes
144144
(Contributed by Levi Sabah, Zackery Spytz and Hugo van Kemenade in
145145
:gh:`73965`.)
146146

147+
* The iterator returned by :meth:`concurrent.futures.Executor.map` is no longer
148+
automatically closed if a function call raises an exception.
149+
Use method :meth:`!close` to explicitly close the iterator.
150+
(Contributed by xzmeng and Serhiy Storchaka in :gh:`108518`.)
151+
147152
New Modules
148153
===========
149154

Lib/concurrent/futures/_base.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,8 @@ def _result_or_cancel(fut, timeout=None):
315315
try:
316316
try:
317317
return (fut.result(timeout), None)
318+
except TimeoutError:
319+
raise
318320
except BaseException as exc:
319321
return (None, exc)
320322
finally:

Lib/test/test_concurrent_futures/executor.py

Lines changed: 24 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -49,48 +49,42 @@ def test_map(self):
4949

5050
def test_map_exception(self):
5151
i = self.executor.map(divmod, [5, 5, 5, 5], [2, 3, 0, 5])
52-
self.assertEqual(i.__next__(), (2, 1))
53-
self.assertEqual(i.__next__(), (1, 2))
54-
self.assertRaises(ZeroDivisionError, i.__next__)
55-
self.assertEqual(i.__next__(), (1, 0))
56-
self.assertRaises(StopIteration, i.__next__)
57-
self.assertRaises(StopIteration, i.__next__)
52+
self.assertEqual(next(i), (2, 1))
53+
self.assertEqual(next(i), (1, 2))
54+
self.assertRaises(ZeroDivisionError, next, i)
55+
self.assertEqual(next(i), (1, 0))
56+
self.assertRaises(StopIteration, next, i)
57+
self.assertRaises(StopIteration, next, i)
5858

5959
i = self.executor.map(divmod, [5, 5, 5, 5], [2, 0, 3, 5], chunksize=3)
60-
self.assertEqual(i.__next__(), (2, 1))
61-
self.assertRaises(ZeroDivisionError, i.__next__)
62-
self.assertEqual(i.__next__(), (1, 2))
63-
self.assertEqual(i.__next__(), (1, 0))
64-
self.assertRaises(StopIteration, i.__next__)
65-
self.assertRaises(StopIteration, i.__next__)
60+
self.assertEqual(next(i), (2, 1))
61+
self.assertRaises(ZeroDivisionError, next, i)
62+
self.assertEqual(next(i), (1, 2))
63+
self.assertEqual(next(i), (1, 0))
64+
self.assertRaises(StopIteration, next, i)
65+
self.assertRaises(StopIteration, next, i)
6666

6767
@support.requires_resource('walltime')
6868
def test_map_timeout(self):
69-
results = []
70-
try:
71-
for i in self.executor.map(time.sleep,
72-
[0, 0, 6],
73-
timeout=5):
74-
results.append(i)
75-
except futures.TimeoutError:
76-
pass
77-
else:
78-
self.fail('expected TimeoutError')
79-
80-
self.assertEqual([None, None], results)
69+
i = self.executor.map(time.sleep, [0, 0, 6, 0], timeout=5)
70+
next(i)
71+
next(i)
72+
self.assertRaises(futures.TimeoutError, next, i)
73+
self.assertRaises(StopIteration, next, i)
74+
self.assertRaises(StopIteration, next, i)
8175

8276
def test_map_close(self):
8377
i = self.executor.map(divmod, [5, 5, 5, 5], [2, 0, 3, 5])
84-
self.assertEqual(i.__next__(), (2, 1))
78+
self.assertEqual(next(i), (2, 1))
8579
i.close()
86-
self.assertRaises(StopIteration, i.__next__)
87-
self.assertRaises(StopIteration, i.__next__)
80+
self.assertRaises(StopIteration, next, i)
81+
self.assertRaises(StopIteration, next, i)
8882

8983
i = self.executor.map(divmod, [5, 5, 5, 5], [2, 0, 3, 5], chunksize=3)
90-
self.assertEqual(i.__next__(), (2, 1))
84+
self.assertEqual(next(i), (2, 1))
9185
i.close()
92-
self.assertRaises(StopIteration, i.__next__)
93-
self.assertRaises(StopIteration, i.__next__)
86+
self.assertRaises(StopIteration, next, i)
87+
self.assertRaises(StopIteration, next, i)
9488

9589
def test_shutdown_race_issue12456(self):
9690
# Issue #12456: race condition at shutdown where trying to post a
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
The iterator returned by :meth:`concurrent.futures.Executor.map` is no longer
2+
automatically closed if a function call raises an exception.
3+
Use method :meth:`!close` to explicitly close the iterator.

0 commit comments

Comments
 (0)