@@ -146,8 +146,27 @@ async def wrapped_coro() -> R:
146146 # Use a timeout to be able to catch asynchronously
147147 # raised dramatiq exceptions (Interrupt).
148148 return future .result (timeout = self .interrupt_check_ival )
149- except concurrent .futures .TimeoutError :
150- continue
149+ except (
150+ # TODO replace with built-in TimeoutError once 3.10 support dropped.
151+ concurrent .futures .TimeoutError
152+ ):
153+ # NOTE: TimeoutError caught here could be from future.result() timing out (i.e. future not done yet),
154+ # or a TimeoutError raised inside the future itself (future is done).
155+ if not future .done ():
156+ # future not done, so .result() must've timed out. continue to wait again.
157+ continue
158+
159+ # If execution reaches here, it means a TimeoutError was caught above, and the future is done.
160+ # There are 3 possibilities here:
161+ # 1. TimeoutError was raised inside the future. This will re-raise it.
162+ # 2. First .result() call timed out, but the future completed by the time .done() was called.
163+ # a) This will return the future's result, or
164+ # b) raise the Exception that happened in the future.
165+ return future .result (timeout = 0 )
166+ # This is outside the 'except' block to avoid any
167+ # "During handling of the above exception, another exception occurred" messages.
168+ # zero timeout used because future is now done.
169+
151170 except Interrupt as e :
152171 # Asynchronously raised from another thread: cancel the
153172 # future.
0 commit comments