Skip to content

Commit

Permalink
Ensure that cancel scopes are entered and exited in the same task
Browse files Browse the repository at this point in the history
Only checks on asyncio; trio support is not there yet.
  • Loading branch information
agronholm committed Mar 29, 2021
1 parent 87bd602 commit eb09096
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 6 deletions.
2 changes: 2 additions & 0 deletions docs/versionhistory.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ This library adheres to `Semantic Versioning 2.0 <http:https://semver.org/>`_.
* ``run_sync_in_worker_thread()`` → ``anyio.to_thread.run_sync()``
* ``run_async_from_thread()`` → ``anyio.from_thread.run()``
* ``run_sync_from_thread()`` → ``anyio.from_thread.run_sync()``
- Added check that cancel scopes are entered and exited in the same task (only done on asyncio for
now)

**3.0.0rc2**

Expand Down
15 changes: 9 additions & 6 deletions src/anyio/_backends/_asyncio.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,19 +265,22 @@ def __exit__(self, exc_type: Optional[Type[BaseException]], exc_val: Optional[Ba
exc_tb: Optional[TracebackType]) -> Optional[bool]:
if not self._active:
raise RuntimeError('This cancel scope is not active')
if current_task() is not self._host_task:
raise RuntimeError('Attempted to exit cancel scope in a different task than it was '
'entered in')

assert self._host_task is not None
host_task_state = _task_states.get(self._host_task)
if host_task_state is None or host_task_state.cancel_scope is not self:
raise RuntimeError("Attempted to exit a cancel scope that isn't the current tasks's "
"current cancel scope")

self._active = False
if self._timeout_handle:
self._timeout_handle.cancel()
self._timeout_handle = None

assert self._host_task is not None
self._tasks.remove(self._host_task)
host_task_state = _task_states.get(self._host_task)
if host_task_state is None or host_task_state.cancel_scope is not self:
raise RuntimeError("Cancel scope task corruption detected.\n"
"This means that you tried to exit a cancel scope that wasn't the"
"last entered cancel scope in the current host task.")

host_task_state.cancel_scope = self._parent_scope

Expand Down
17 changes: 17 additions & 0 deletions tests/test_taskgroups.py
Original file line number Diff line number Diff line change
Expand Up @@ -867,3 +867,20 @@ async def test_cancelscope_exit_before_enter():
"""Test that a RuntimeError is raised if one tries to exit a cancel scope before entering."""
scope = CancelScope()
pytest.raises(RuntimeError, scope.__exit__, None, None, None)


@pytest.mark.parametrize('anyio_backend', ['asyncio']) # trio does not check for this yet
async def test_cancelscope_exit_in_wrong_task():
async def enter_scope(scope):
scope.__enter__()

async def exit_scope(scope):
scope.__exit__(None, None, None)

scope = CancelScope()
async with create_task_group() as tg:
tg.spawn(enter_scope, scope)

with pytest.raises(RuntimeError):
async with create_task_group() as tg:
tg.spawn(exit_scope, scope)

0 comments on commit eb09096

Please sign in to comment.