Bug report
gh-108518 documented that if a call raises, :meth:Executor.map no longer
cancels the remaining calls and iteration can continue. That holds for every
exception except :exc:TimeoutError, which still aborts and cancels the rest.
from concurrent.futures import ThreadPoolExecutor
def f(x):
if x == 2:
raise TimeoutError # e.g. a socket.timeout from one network call
return x
with ThreadPoolExecutor() as ex:
it = ex.map(f, [1, 2, 3, 4])
print(next(it)) # 1
try: next(it) # raises TimeoutError for x=2
except TimeoutError: pass
print(list(it)) # [] -- 3 and 4 were cancelled
With any other exception (e.g. ValueError) the last line prints [3, 4].
socket.timeout and asyncio.TimeoutError are aliases of
:exc:TimeoutError, so a single timed-out call in a mapped batch silently
drops all remaining work. ProcessPoolExecutor is unaffected.
The cause is in _result_or_cancel: a TimeoutError from the callable is
indistinguishable by type from the map(timeout=...) wait timeout, so it is
re-raised instead of being returned like other exceptions.
Linked PRs
Bug report
gh-108518 documented that if a call raises, :meth:
Executor.mapno longercancels the remaining calls and iteration can continue. That holds for every
exception except :exc:
TimeoutError, which still aborts and cancels the rest.With any other exception (e.g.
ValueError) the last line prints[3, 4].socket.timeoutandasyncio.TimeoutErrorare aliases of:exc:
TimeoutError, so a single timed-out call in a mapped batch silentlydrops all remaining work.
ProcessPoolExecutoris unaffected.The cause is in
_result_or_cancel: aTimeoutErrorfrom the callable isindistinguishable by type from the
map(timeout=...)wait timeout, so it isre-raised instead of being returned like other exceptions.
Linked PRs