Skip to content

Commit

Permalink
Fixed cancellation delivery sometimes incrementing the cancel count o…
Browse files Browse the repository at this point in the history
…f the wrong cancel scope (#718)

Fixes #716.
  • Loading branch information
agronholm authored Apr 15, 2024
1 parent cf09e40 commit 3e6a22c
Show file tree
Hide file tree
Showing 3 changed files with 17 additions and 1 deletion.
3 changes: 3 additions & 0 deletions docs/versionhistory.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ This library adheres to `Semantic Versioning 2.0 <http://semver.org/>`_.
when cancelling a task in a TaskGroup created with the ``start()`` method before
the first checkpoint is reached after calling ``task_status.started()``
(`#706 <https://github.com/agronholm/anyio/issues/706>`_; PR by Dominik Schwabe)
- Fixed cancellation delivery on asyncio incrementing the wrong cancel scope's
cancellation counter when cascading a cancel operation to a child scope, thus failing
to uncancel the host task (`#716 <https://github.com/agronholm/anyio/issues/716>`_)
- Fixed erroneous ``TypedAttributeLookupError`` if a typed attribute getter raises
``KeyError``
- Fixed the asyncio backend not respecting the ``PYTHONASYNCIODEBUG`` environment
Expand Down
2 changes: 1 addition & 1 deletion src/anyio/_backends/_asyncio.py
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ def _deliver_cancellation(self, origin: CancelScope) -> bool:
if task is not current and (task is self._host_task or _task_started(task)):
waiter = task._fut_waiter # type: ignore[attr-defined]
if not isinstance(waiter, asyncio.Future) or not waiter.done():
self._cancel_calls += 1
origin._cancel_calls += 1
if sys.version_info >= (3, 9):
task.cancel(f"Cancelled by cancel scope {id(origin):x}")
else:
Expand Down
13 changes: 13 additions & 0 deletions tests/test_taskgroups.py
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,19 @@ async def test_cancel_before_entering_scope() -> None:
pytest.fail("execution should not reach this point")


@pytest.mark.xfail(
sys.version_info < (3, 11), reason="Requires asyncio.Task.cancelling()"
)
@pytest.mark.parametrize("anyio_backend", ["asyncio"])
async def test_cancel_counter_nested_scopes() -> None:
with CancelScope() as root_scope:
with CancelScope():
root_scope.cancel()
await sleep(0.5)

assert not cast(asyncio.Task, asyncio.current_task()).cancelling()


async def test_exception_group_children() -> None:
with pytest.raises(BaseExceptionGroup) as exc:
async with create_task_group() as tg:
Expand Down

0 comments on commit 3e6a22c

Please sign in to comment.