Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

According to the docs :

If the call hasn’t yet completed then this method will wait up to timeout seconds. If the call hasn’t completed in timeout seconds, then a TimeoutError will be raised.

But clearly this is a False statement and doesn't represent the actual behavior:

from concurrent.futures import ThreadPoolExecutor, as_completed
from time import sleep
def my_worker(sleep_time, expected_timeout):
    sleep(sleep_time)
    print(f'slept for {sleep_time} | expected timeout: {expected_timeout}')
if __name__ == '__main__':
    timeout = 1
    with ThreadPoolExecutor(2) as executor:
        futures = [executor.submit(my_worker, 3, timeout)]
        for future in as_completed(futures):
            future.result(timeout=timeout)

which results in:

slept for 3 | expected timeout: 1

There are other questions addressing a variation of the same issue with concurrent.futures objects, but none provide any clarification of the timeout argument in Future.result.

Even the timeout argument in concurrent.futures.as_completed mentioned in the other questions is not working as expected. According to the docs:

The returned iterator raises a TimeoutError if next() is called and the result isn’t available after timeout seconds from the original call to as_completed(). timeout can be an int or float.

This sets me to expect a timeout exception is to be raised after whatever timeout is specified of as_completed call, disregarding how many tasks are pending. This also doesn't work as expected:

timeout = 1
with ThreadPoolExecutor(2) as executor:
    futures = [executor.submit(my_worker, 3, timeout) for _ in range(5)]
    for future in as_completed(futures, timeout):  # should timeout after one second
        future.result()

What actually happens: as_completed ignores the timeout and awaits all tasks to finish before raising an error:

slept for 3 | expected timeout: 1
slept for 3 | expected timeout: 1
slept for 3 | expected timeout: 1
slept for 3 | expected timeout: 1
slept for 3 | expected timeout: 1
Traceback (most recent call last):
  File "my_file.py", line 16, in <module>
    completed.__next__()
  File "/usr/local/Cellar/python@3.11/3.11.3/Frameworks/Python.framework/Versions/3.11/lib/python3.11/concurrent/futures/_base.py", line 239, in as_completed
    raise TimeoutError(
TimeoutError: 5 (of 5) futures unfinished

Even the error message indicating that 5/5 tasks failed to complete is not true, otherwise these finished - timeout prompts wouldn't have shown.

For the first implementation, you are calling Future.result inside as_completed. As timeout starts from the moment Future.result is called, and all threads are already finished by then, no error is raised. If the keyword argument is placed in as_completed instead, the exception is thrown as expected.

As for the second implementation, documentation for ThreadPoolExecutor says that

All threads enqueued to ThreadPoolExecutor will be joined before the interpreter can exit.

So the error is raised at the expected moment, just that Python waits until all threads are finished before throwing TimeoutError.

As a sanity check,

timeout = 1
with ThreadPoolExecutor(2) as executor:
    futures = [executor.submit(my_worker, 3, timeout) for _ in range(5)]
        for future in as_completed(futures, timeout=timeout):  # should timeout after one second
            future.result()
    except Exception as e:
        print(type(e).__name__)
        print(e)

outputs

TimeoutError
5 (of 5) futures unfinished
slept for 3 | expected timeout: 1
slept for 3 | expected timeout: 1
slept for 3 | expected timeout: 1
slept for 3 | expected timeout: 1
slept for 3 | expected timeout: 1

And it seems that everything does work according to the documentation. Though I do agree with you that this isn't very intuitive.

Thanks for contributing an answer to Stack Overflow!

  • Please be sure to answer the question. Provide details and share your research!

But avoid

  • Asking for help, clarification, or responding to other answers.
  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.