From 382a5635bd10c237c3e23e346b21cde27e48d7fa Mon Sep 17 00:00:00 2001 From: romasku Date: Fri, 15 May 2020 23:12:05 +0300 Subject: bpo-40607: Reraise exception during task cancelation in asyncio.wait_for() (GH-20054) Currently, if asyncio.wait_for() timeout expires, it cancels inner future and then always raises TimeoutError. In case those future is task, it can handle cancelation mannually, and those process can lead to some other exception. Current implementation silently loses thoses exception. To resolve this, wait_for will check was the cancelation successfull or not. In case there was exception, wait_for will reraise it. Co-authored-by: Roman Skurikhin --- Lib/test/test_asyncio/test_tasks.py | 55 ++++++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 4 deletions(-) (limited to 'Lib/test/test_asyncio/test_tasks.py') diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index 6eb6b46ec8..0f8d921c5b 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -80,6 +80,12 @@ class CoroLikeObject: 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 @@ -904,12 +910,53 @@ class BaseTaskTests: 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() -- cgit v1.2.1