Skip to content

Commit f4a403c

Browse files
committed
Fixed task group getting cancelled if start() gets cancelled
This change fixes the problem by special casing the situation where the Future backing `task_status` was cancelled which only happens when the host task is cancelled. Fixes #685. Fixes #701.
1 parent cf09e40 commit f4a403c

File tree

3 files changed

+32
-8
lines changed

3 files changed

+32
-8
lines changed

docs/versionhistory.rst

+9-4
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,15 @@ This library adheres to `Semantic Versioning 2.0 <http://semver.org/>`_.
88
- Added the ``BlockingPortalProvider`` class to aid with constructing synchronous
99
counterparts to asynchronous interfaces that would otherwise require multiple blocking
1010
portals
11-
- Fixed erroneous ``RuntimeError: called 'started' twice on the same task status``
12-
when cancelling a task in a TaskGroup created with the ``start()`` method before
13-
the first checkpoint is reached after calling ``task_status.started()``
14-
(`#706 <https://github.com/agronholm/anyio/issues/706>`_; PR by Dominik Schwabe)
11+
- Fixed two bugs with ``TaskGroup.start()`` on asyncio:
12+
13+
* Fixed erroneous ``RuntimeError: called 'started' twice on the same task status``
14+
when cancelling a task in a TaskGroup created with the ``start()`` method before
15+
the first checkpoint is reached after calling ``task_status.started()``
16+
(`#706 <https://github.com/agronholm/anyio/issues/706>`_; PR by Dominik Schwabe)
17+
* Fixed the entire task group being cancelled if a ``TaskGroup.start()`` call gets
18+
cancelled (`#685 <https://github.com/agronholm/anyio/issues/685>`_,
19+
`#701 <https://github.com/agronholm/anyio/issues/710>`_)
1520
- Fixed erroneous ``TypedAttributeLookupError`` if a typed attribute getter raises
1621
``KeyError``
1722
- Fixed the asyncio backend not respecting the ``PYTHONASYNCIODEBUG`` environment

src/anyio/_backends/_asyncio.py

+4
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,10 @@ def task_done(_task: asyncio.Task) -> None:
714714
exc = e
715715

716716
if exc is not None:
717+
# The future can only be cancelled if the host task was cancelled
718+
if task_status_future is not None and task_status_future.cancelled():
719+
return
720+
717721
if task_status_future is None or task_status_future.done():
718722
if not isinstance(exc, CancelledError):
719723
self._exceptions.append(exc)

tests/test_taskgroups.py

+19-4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
get_current_task,
2323
move_on_after,
2424
sleep,
25+
sleep_forever,
2526
wait_all_tasks_blocked,
2627
)
2728
from anyio.abc import TaskGroup, TaskStatus
@@ -127,7 +128,6 @@ async def test_no_called_started_twice() -> None:
127128
async def taskfunc(*, task_status: TaskStatus) -> None:
128129
task_status.started()
129130

130-
# anyio>4.3.0 should not raise "RuntimeError: called 'started' twice on the same task status"
131131
async with create_task_group() as tg:
132132
coro = tg.start(taskfunc)
133133
tg.cancel_scope.cancel()
@@ -196,9 +196,6 @@ async def taskfunc(*, task_status: TaskStatus) -> None:
196196
assert not finished
197197

198198

199-
@pytest.mark.xfail(
200-
sys.version_info < (3, 9), reason="Requires a way to detect cancellation source"
201-
)
202199
@pytest.mark.parametrize("anyio_backend", ["asyncio"])
203200
async def test_start_native_host_cancelled() -> None:
204201
started = finished = False
@@ -1347,6 +1344,24 @@ async def wait_cancel() -> None:
13471344
await cancelled.wait()
13481345

13491346

1347+
async def test_start_cancels_parent_scope() -> None:
1348+
"""Regression test for #685 / #710."""
1349+
started: bool = False
1350+
1351+
async def in_task_group(task_status: TaskStatus[None]) -> None:
1352+
nonlocal started
1353+
started = True
1354+
await sleep_forever()
1355+
1356+
async with create_task_group() as tg:
1357+
with CancelScope() as inner_scope:
1358+
inner_scope.cancel()
1359+
await tg.start(in_task_group)
1360+
1361+
assert started
1362+
assert not tg.cancel_scope.cancel_called
1363+
1364+
13501365
class TestTaskStatusTyping:
13511366
"""
13521367
These tests do not do anything at run time, but since the test suite is also checked

0 commit comments

Comments
 (0)