Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit e16d4ed

Browse filesBrowse files
gh-95166: cancel map waited on future on timeout (GH-95169)
Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com>
1 parent b8b2990 commit e16d4ed
Copy full SHA for e16d4ed

File tree

Expand file treeCollapse file tree

3 files changed

+42
-2
lines changed
Filter options
Expand file treeCollapse file tree

3 files changed

+42
-2
lines changed

‎Lib/concurrent/futures/_base.py

Copy file name to clipboardExpand all lines: Lib/concurrent/futures/_base.py
+14-2Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,18 @@ def wait(fs, timeout=None, return_when=ALL_COMPLETED):
310310
done.update(waiter.finished_futures)
311311
return DoneAndNotDoneFutures(done, fs - done)
312312

313+
314+
def _result_or_cancel(fut, timeout=None):
315+
try:
316+
try:
317+
return fut.result(timeout)
318+
finally:
319+
fut.cancel()
320+
finally:
321+
# Break a reference cycle with the exception in self._exception
322+
del fut
323+
324+
313325
class Future(object):
314326
"""Represents the result of an asynchronous computation."""
315327

@@ -604,9 +616,9 @@ def result_iterator():
604616
while fs:
605617
# Careful not to keep a reference to the popped future
606618
if timeout is None:
607-
yield fs.pop().result()
619+
yield _result_or_cancel(fs.pop())
608620
else:
609-
yield fs.pop().result(end_time - time.monotonic())
621+
yield _result_or_cancel(fs.pop(), end_time - time.monotonic())
610622
finally:
611623
for future in fs:
612624
future.cancel()

‎Lib/test/test_concurrent_futures.py

Copy file name to clipboardExpand all lines: Lib/test/test_concurrent_futures.py
+27Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -932,6 +932,33 @@ def submit(pool):
932932
with futures.ProcessPoolExecutor(1, mp_context=mp.get_context('fork')) as workers:
933933
workers.submit(tuple)
934934

935+
def test_executor_map_current_future_cancel(self):
936+
stop_event = threading.Event()
937+
log = []
938+
939+
def log_n_wait(ident):
940+
log.append(f"{ident=} started")
941+
try:
942+
stop_event.wait()
943+
finally:
944+
log.append(f"{ident=} stopped")
945+
946+
with self.executor_type(max_workers=1) as pool:
947+
# submit work to saturate the pool
948+
fut = pool.submit(log_n_wait, ident="first")
949+
try:
950+
with contextlib.closing(
951+
pool.map(log_n_wait, ["second", "third"], timeout=0)
952+
) as gen:
953+
with self.assertRaises(TimeoutError):
954+
next(gen)
955+
finally:
956+
stop_event.set()
957+
fut.result()
958+
# ident='second' is cancelled as a result of raising a TimeoutError
959+
# ident='third' is cancelled because it remained in the collection of futures
960+
self.assertListEqual(log, ["ident='first' started", "ident='first' stopped"])
961+
935962

936963
class ProcessPoolExecutorTest(ExecutorTest):
937964

+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix :meth:`concurrent.futures.Executor.map` to cancel the currently waiting on future on an error - e.g. TimeoutError or KeyboardInterrupt.

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.