https://github.com/python/cpython/commit/a8e1d25355004ba8790e34dbf7fb5268674e7f08
commit: a8e1d25355004ba8790e34dbf7fb5268674e7f08
branch: main
author: tonghuaroot (童话) <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-17T15:44:40+03:00
summary:
gh-155852: Do not cancel remaining Executor.map calls on a callable's
TimeoutError (GH-155853)
A TimeoutError raised by the mapped callable was re-raised like the
map(timeout=...) wait timeout, aborting the iteration and cancelling the
remaining calls, unlike every other exception since gh-108518. A wait timeout
only occurs while the future is still running, so a TimeoutError from an
already-finished future is treated as the callable's own result.
files:
M Lib/concurrent/futures/_base.py
M Lib/test/test_concurrent_futures/executor.py
diff --git a/Lib/concurrent/futures/_base.py b/Lib/concurrent/futures/_base.py
index cc335d9aa1ea55d..e728b8e0a91f744 100644
--- a/Lib/concurrent/futures/_base.py
+++ b/Lib/concurrent/futures/_base.py
@@ -309,10 +309,13 @@ def wait(fs, timeout=None, return_when=ALL_COMPLETED):
def _result_or_cancel(fut, timeout=None):
try:
try:
- return (fut.result(timeout), None)
- except TimeoutError:
- raise
- except BaseException as exc:
+ # fut.exception() returns the call's own error but raises
+ # TimeoutError only for a map() timeout.
+ exc = fut.exception(timeout)
+ if exc is not None:
+ return (None, exc)
+ return (fut.result(), None)
+ except CancelledError as exc:
return (None, exc)
finally:
fut.cancel()
diff --git a/Lib/test/test_concurrent_futures/executor.py
b/Lib/test/test_concurrent_futures/executor.py
index 5d9f27c83bf9a81..ff7bd0db0c2199c 100644
--- a/Lib/test/test_concurrent_futures/executor.py
+++ b/Lib/test/test_concurrent_futures/executor.py
@@ -29,6 +29,12 @@ def raiser(exception, msg='std'):
raise exception(msg)
+def timeout_on_one(x):
+ if x == 1:
+ raise TimeoutError
+ return x
+
+
class FalseyBoolException(Exception):
def __bool__(self):
return False
@@ -87,6 +93,20 @@ def test_map_exception(self):
self.assertRaises(StopIteration, next, i)
self.assertRaises(StopIteration, next, i)
+ @warnings_helper.ignore_fork_in_thread_deprecation_warnings()
+ def test_map_timeout_from_callable(self):
+ # A TimeoutError from the callable is not the map() timeout, whether
+ # or not a map() timeout is set.
+ for timeout in (None, support.SHORT_TIMEOUT):
+ with self.subTest(timeout=timeout):
+ i = self.executor.map(timeout_on_one, [0, 1, 2, 3],
+ timeout=timeout)
+ self.assertEqual(next(i), 0)
+ self.assertRaises(TimeoutError, next, i)
+ self.assertEqual(next(i), 2)
+ self.assertEqual(next(i), 3)
+ self.assertRaises(StopIteration, next, i)
+
@warnings_helper.ignore_fork_in_thread_deprecation_warnings()
@support.requires_resource('walltime')
def test_map_timeout(self):
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]