Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Raise a single Cancelled from trio TaskGroups #593

Merged
merged 5 commits into from
Jul 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/versionhistory.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ This library adheres to `Semantic Versioning 2.0 <http://semver.org/>`_.
tasks were spawned and an outer cancellation scope had been cancelled before
- Ensured that exiting a ``TaskGroup`` always hits a yield point, regardless of
whether there are running child tasks to be waited on
- Task groups on all backends now raise a single cancellation exception when an outer
cancel scope is cancelled, and no exceptions other than cancellation exceptions are
raised in the group
- **BACKWARDS INCOMPATIBLE** Changes the pytest plugin to run all tests and fixtures in
the same task, allowing fixtures to set context variables for tests and other fixtures
- **BACKWARDS INCOMPATIBLE** Changed ``anyio.Path.relative_to()`` and
Expand Down
11 changes: 11 additions & 0 deletions src/anyio/_backends/_trio.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import array
import math
import socket
import sys
import types
from collections.abc import AsyncIterator, Iterable
from concurrent.futures import Future
Expand Down Expand Up @@ -62,6 +63,9 @@
from ..abc._eventloop import AsyncBackend
from ..streams.memory import MemoryObjectSendStream

if sys.version_info < (3, 11):
from exceptiongroup import BaseExceptionGroup

T = TypeVar("T")
T_Retval = TypeVar("T_Retval")
T_SockAddr = TypeVar("T_SockAddr", str, IPSockAddrType)
Expand Down Expand Up @@ -156,6 +160,13 @@ async def __aexit__(
) -> bool | None:
try:
return await self._nursery_manager.__aexit__(exc_type, exc_val, exc_tb)
except BaseExceptionGroup as exc:
_, rest = exc.split(trio.Cancelled)
if not rest:
cancelled_exc = trio.Cancelled._create() # type: ignore [attr-defined]
raise cancelled_exc from exc

raise
finally:
self._active = False

Expand Down
22 changes: 22 additions & 0 deletions tests/test_taskgroups.py
Original file line number Diff line number Diff line change
Expand Up @@ -1129,6 +1129,28 @@ async def main() -> NoReturn:
assert not caplog.messages


async def test_single_cancellation_exc() -> None:
"""
Test that only a single cancellation exception bubbles out of the task group when
case it was cancelled via an outer scope and no actual errors were raised.

"""
with CancelScope() as outer:
try:
async with create_task_group() as tg:
tg.start_soon(sleep, 5)
await wait_all_tasks_blocked()
outer.cancel()
await sleep(5)
except BaseException as exc:
if isinstance(exc, get_cancelled_exc_class()):
raise

pytest.fail(f"Raised the wrong type of exception: {exc}")
else:
pytest.fail("Did not raise a cancellation exception")


async def test_start_soon_parent_id() -> None:
root_task_id = get_current_task().id
parent_id: int | None = None
Expand Down