diff --git a/Lib/asyncio/timeouts.py b/Lib/asyncio/timeouts.py index 09342dc7c1310b..12b11b84193b27 100644 --- a/Lib/asyncio/timeouts.py +++ b/Lib/asyncio/timeouts.py @@ -89,6 +89,7 @@ async def __aenter__(self) -> "Timeout": self._state = _State.ENTERED self._task = task self._cancelling = self._task.cancelling() + self._must_cancel = self._task._must_cancel self.reschedule(self._when) return self @@ -106,10 +107,13 @@ async def __aexit__( if self._state is _State.EXPIRING: self._state = _State.EXPIRED - - if self._task.uncancel() <= self._cancelling and exc_type is not None: - # Since there are no new cancel requests, we're - # handling this. + if ( + self._task.uncancel() <= self._cancelling + and exc_type is not None + and not self._must_cancel + ): + # Since there are no new cancel requests + # and the task doesn't _have to_ raise CancelledError, we're handling this. if issubclass(exc_type, exceptions.CancelledError): raise TimeoutError from exc_val elif exc_val is not None: diff --git a/Lib/test/test_asyncio/test_timeouts.py b/Lib/test/test_asyncio/test_timeouts.py index 3ba84d63b2ca5f..bb9c9a39c9f883 100644 --- a/Lib/test/test_asyncio/test_timeouts.py +++ b/Lib/test/test_asyncio/test_timeouts.py @@ -4,6 +4,7 @@ import time import asyncio +from asyncio import CancelledError from test.test_asyncio.utils import await_without_task @@ -298,6 +299,18 @@ async def test_nested_timeout_in_finally(self): self.assertIs(e2.__context__, e22) async def test_timeout_after_cancellation(self): + asyncio.current_task().cancel() + with self.assertRaises(asyncio.CancelledError): + async with asyncio.timeout(0.1): + await asyncio.sleep(1) + + async def test_timeout_immediately_after_cancellation(self): + asyncio.current_task().cancel() + with self.assertRaises(asyncio.CancelledError): + async with asyncio.timeout(0.0): + await asyncio.sleep(1) + + async def test_timeout_while_handling_cancellation(self): try: asyncio.current_task().cancel() await asyncio.sleep(1) # work which will be cancelled @@ -308,7 +321,7 @@ async def test_timeout_after_cancellation(self): async with asyncio.timeout(0.0): await asyncio.sleep(1) # some cleanup - async def test_cancel_in_timeout_after_cancellation(self): + async def test_cancel_in_timeout_while_handling_cancellation(self): try: asyncio.current_task().cancel() await asyncio.sleep(1) # work which will be cancelled