wrap it in :func:`shield`.
The function will wait until the future is actually cancelled,
- so the total wait time may exceed the *timeout*.
+ so the total wait time may exceed the *timeout*. If an exception
+ happens during cancellation, it is propagated.
If the wait is cancelled, the future *aw* is also cancelled.
# after wait_for() returns.
# See https://bugs.python.org/issue32751
await _cancel_and_wait(fut, loop=loop)
- raise exceptions.TimeoutError()
+ # In case task cancellation failed with some
+ # exception, we should re-raise it
+ # See https://bugs.python.org/issue40607
+ try:
+ fut.result()
+ except exceptions.CancelledError as exc:
+ raise exceptions.TimeoutError() from exc
+ else:
+ raise exceptions.TimeoutError()
finally:
timeout_handle.cancel()
return self
+# The following value can be used as a very small timeout:
+# it passes check "timeout > 0", but has almost
+# no effect on the test performance
+_EPSILON = 0.0001
+
+
class BaseTaskTests:
Task = None
inner_task = self.new_task(loop, inner())
- with self.assertRaises(asyncio.TimeoutError):
- await asyncio.wait_for(inner_task, timeout=0.1)
+ await asyncio.wait_for(inner_task, timeout=_EPSILON)
- self.assertTrue(task_done)
+ with self.assertRaises(asyncio.TimeoutError) as cm:
+ loop.run_until_complete(foo())
- loop.run_until_complete(foo())
+ self.assertTrue(task_done)
+ chained = cm.exception.__context__
+ self.assertEqual(type(chained), asyncio.CancelledError)
+
+ def test_wait_for_reraises_exception_during_cancellation(self):
+ loop = asyncio.new_event_loop()
+ self.addCleanup(loop.close)
+
+ class FooException(Exception):
+ pass
+
+ async def foo():
+ async def inner():
+ try:
+ await asyncio.sleep(0.2)
+ finally:
+ raise FooException
+
+ inner_task = self.new_task(loop, inner())
+
+ await asyncio.wait_for(inner_task, timeout=_EPSILON)
+
+ with self.assertRaises(FooException):
+ loop.run_until_complete(foo())
+
+ def test_wait_for_raises_timeout_error_if_returned_during_cancellation(self):
+ loop = asyncio.new_event_loop()
+ self.addCleanup(loop.close)
+
+ async def foo():
+ async def inner():
+ try:
+ await asyncio.sleep(0.2)
+ except asyncio.CancelledError:
+ return 42
+
+ inner_task = self.new_task(loop, inner())
+
+ await asyncio.wait_for(inner_task, timeout=_EPSILON)
+
+ with self.assertRaises(asyncio.TimeoutError):
+ loop.run_until_complete(foo())
def test_wait_for_self_cancellation(self):
loop = asyncio.new_event_loop()