Skip to content

Commit

Permalink
Disable cancel scope reuse
Browse files Browse the repository at this point in the history
To keep our options open for adding a smarter `was_cancelled` property, per discussion in python-trio#886.
  • Loading branch information
oremanj committed Feb 7, 2019
1 parent 0a815a0 commit 90b7fcc
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 41 deletions.
29 changes: 5 additions & 24 deletions trio/_core/_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,23 +154,9 @@ class CancelScope:
passed to another, so that the first task can later cancel some work
inside the second.
Cancel scopes are reusable: once you exit the ``with`` block, you
can use the same :class:`CancelScope` object to wrap another chunk
of work. (The cancellation state doesn't change; once a cancel
scope becomes cancelled, it stays cancelled.) This can be useful
if you want a cancellation to be able to interrupt some operations
in a loop but not others::
cancel_scope = trio.CancelScope(deadline=...)
while True:
with cancel_scope:
request = await get_next_request()
response = await handle_request(request)
await send_response(response)
Cancel scopes are *not* reentrant: you can't enter a second
``with`` block using the same :class:`CancelScope` while the first
one is still active. (You'll get a :exc:`RuntimeError` if you try.)
Cancel scopes are not reusable or reentrant; that is, each cancel
scope can be used for at most one ``with`` block. (You'll get a
:exc:`RuntimeError` if you violate this rule.)
The :class:`CancelScope` constructor takes initial values for the
cancel scope's :attr:`deadline` and :attr:`shield` attributes; these
Expand All @@ -193,14 +179,10 @@ def __enter__(self):
task = _core.current_task()
if self._scope_task is not None:
raise RuntimeError(
"cancel scope may not be entered while it is already "
"active{}".format(
"" if self._scope_task is task else
" in another task ({!r})".format(self._scope_task.name)
)
"cancel scopes may not be reused or reentered "
"(first used in task {!r})".format(self._scope_task.name)
)
self._scope_task = task
self.cancelled_caught = False
if current_time() >= self._deadline:
self.cancel_called = True
with self._might_change_effective_deadline():
Expand Down Expand Up @@ -390,7 +372,6 @@ def _close(self, exc):
exc = MultiError.filter(self._exc_filter, exc)
with self._might_change_effective_deadline():
self._remove_task(self._scope_task)
self._scope_task = None
return exc


Expand Down
28 changes: 11 additions & 17 deletions trio/_core/tests/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -875,45 +875,39 @@ async def sleep_until_cancelled(scope):
await wait_all_tasks_blocked()
scope.cancel()

# Cancel after exit, then reuse
# Can't reuse
with _core.CancelScope() as scope:
await _core.checkpoint()
scope.cancel()
await _core.checkpoint()
assert scope.cancel_called
assert not scope.cancelled_caught
with scope:
await _core.checkpoint()
assert scope.cancelled_caught
with scope:
assert not scope.cancelled_caught
await _core.checkpoint()
assert scope.cancelled_caught
with pytest.raises(RuntimeError) as exc_info:
with scope:
pass # pragma: no cover
assert "reused or reentered" in str(exc_info.value)

# Attempts to reenter throw an error
# Can't reenter
with _core.CancelScope() as scope:
with pytest.raises(RuntimeError) as exc_info:
with scope:
pass # pragma: no cover
assert "may not be entered while it is already active" in str(
exc_info.value
)
assert "reused or reentered" in str(exc_info.value)

# Attempts to enter from two tasks simultaneously throw an error
# Can't enter from multiple tasks simultaneously
scope = _core.CancelScope()
async def enter_scope():
with scope:
await sleep_forever()

async with _core.open_nursery() as nursery:
nursery.start_soon(enter_scope)
nursery.start_soon(enter_scope, name="this one")
await wait_all_tasks_blocked()

with pytest.raises(RuntimeError) as exc_info:
with scope:
pass # pragma: no cover
assert "while it is already active in another task" in str(
exc_info.value
)
assert "first used in task 'this one'" in str(exc_info.value)
nursery.cancel_scope.cancel()


Expand Down

0 comments on commit 90b7fcc

Please sign in to comment.