diff --git a/CHANGES/8908.bugfix.rst b/CHANGES/8908.bugfix.rst new file mode 100644 index 00000000000..0eb450431db --- /dev/null +++ b/CHANGES/8908.bugfix.rst @@ -0,0 +1 @@ +Fixed ``CancelledError`` from one cleanup context stopping other contexts from completing -- by :user:`Dreamsorcerer`. diff --git a/aiohttp/web_app.py b/aiohttp/web_app.py index a35ca214572..516d5e619b5 100644 --- a/aiohttp/web_app.py +++ b/aiohttp/web_app.py @@ -430,7 +430,7 @@ async def _on_cleanup(self, app: Application) -> None: await it.__anext__() except StopAsyncIteration: pass - except Exception as exc: + except (Exception, asyncio.CancelledError) as exc: errors.append(exc) else: errors.append(RuntimeError(f"{it!r} has more than one 'yield'")) diff --git a/docs/web_advanced.rst b/docs/web_advanced.rst index a4030d341be..76fc3ea57f1 100644 --- a/docs/web_advanced.rst +++ b/docs/web_advanced.rst @@ -1068,13 +1068,10 @@ below:: async with client.pubsub() as pubsub: await pubsub.subscribe(channel) while True: - try: - msg = await pubsub.get_message(ignore_subscribe_messages=True) - if msg is not None: - for ws in app["websockets"]: - await ws.send_str("{}: {}".format(channel, msg)) - except asyncio.CancelledError: - break + msg = await pubsub.get_message(ignore_subscribe_messages=True) + if msg is not None: + for ws in app["websockets"]: + await ws.send_str("{}: {}".format(channel, msg)) async def background_tasks(app): @@ -1083,7 +1080,8 @@ below:: yield app[redis_listener].cancel() - await app[redis_listener] + with contextlib.suppress(asyncio.CancelledError): + await app[redis_listener] app = web.Application() diff --git a/tests/test_web_app.py b/tests/test_web_app.py index 14e8be404db..28e67677c2b 100644 --- a/tests/test_web_app.py +++ b/tests/test_web_app.py @@ -1,5 +1,5 @@ import asyncio -from typing import Any, AsyncIterator, Callable, Iterator, NoReturn +from typing import Any, AsyncIterator, Callable, Iterator, NoReturn, Type from unittest import mock import pytest @@ -334,7 +334,10 @@ async def fail_ctx(app: web.Application) -> AsyncIterator[NoReturn]: assert ctx_state == "CLEAN" -async def test_cleanup_ctx_exception_on_cleanup_multiple() -> None: +@pytest.mark.parametrize("exc_cls", (Exception, asyncio.CancelledError)) +async def test_cleanup_ctx_exception_on_cleanup_multiple( + exc_cls: Type[BaseException], +) -> None: app = web.Application() out = [] @@ -346,7 +349,7 @@ async def inner(app: web.Application) -> AsyncIterator[None]: yield None out.append("post_" + str(num)) if fail: - raise Exception("fail_" + str(num)) + raise exc_cls("fail_" + str(num)) return inner