gh-96037: Always insert TimeoutError when exit an expired asyncio.timeout() block (GH-113819)

If other exception was raised during exiting an expired
asyncio.timeout() block, insert TimeoutError in the exception context
just above the CancelledError.
This commit is contained in:
Serhiy Storchaka 2024-01-10 12:50:31 +02:00 committed by GitHub
parent ab0ad62038
commit aef4a1203c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 133 additions and 13 deletions

View file

@ -110,10 +110,15 @@ class Timeout:
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 issubclass(exc_type, exceptions.CancelledError):
# Since there are no new cancel requests, we're
# handling this.
raise TimeoutError from exc_val
elif exc_val is not None:
self._insert_timeout_error(exc_val)
if isinstance(exc_val, ExceptionGroup):
for exc in exc_val.exceptions:
self._insert_timeout_error(exc)
elif self._state is _State.ENTERED:
self._state = _State.EXITED
@ -126,6 +131,16 @@ class Timeout:
# drop the reference early
self._timeout_handler = None
@staticmethod
def _insert_timeout_error(exc_val: BaseException) -> None:
while exc_val.__context__ is not None:
if isinstance(exc_val.__context__, exceptions.CancelledError):
te = TimeoutError()
te.__context__ = te.__cause__ = exc_val.__context__
exc_val.__context__ = te
break
exc_val = exc_val.__context__
def timeout(delay: Optional[float]) -> Timeout:
"""Timeout async context manager.