Skip to content

Commit

Permalink
Strengthening against external cancellation
Browse files Browse the repository at this point in the history
  • Loading branch information
Jonathan Martin committed Nov 6, 2018
1 parent a295853 commit 97f2b29
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 13 deletions.
56 changes: 45 additions & 11 deletions tests/test_traio.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,34 @@ async def run_me():
raise Exception('should have raised')


@pytest.mark.asyncio
async def test_external_cancel_nasty():
"""Raise an external cancellation with task which fails cancelling"""
async def nasty():
try:
await asyncio.sleep(10)
except asyncio.CancelledError:
# Something bad happens
raise ValueError('boom')

async def run_me():
n = Nursery()
async with n:
n.start_soon(nasty())

assert n.state == State.CANCELLED

task = asyncio.ensure_future(run_me())

try:
await asyncio.wait_for(task, 0.1)
except asyncio.TimeoutError:
with pytest.raises(asyncio.CancelledError):
await task
else:
raise Exception('should have raised')


@pytest.mark.asyncio
async def test_internal_cancel():
"""Test an internal cancellation"""
Expand All @@ -186,17 +214,6 @@ async def test_cancel_not_started():
n.cancel()


@pytest.mark.asyncio
async def test_cancel_already_finished():
"""Cancel a finished nursery"""
n = Nursery()
async with n:
pass

with pytest.raises(AssertionError):
n.cancel()


@pytest.mark.asyncio
async def test_master():
"""Test if a master correctly cancels pending tasks"""
Expand Down Expand Up @@ -286,3 +303,20 @@ async def trivial():
after = time.time()

assert 0.1 < (after - before) < 0.5


@pytest.mark.asyncio
async def test_cancelling_going_bad():
"""Test cancelling a pending task, but things go wrong..."""
async def nasty():
try:
await asyncio.sleep(10)
except asyncio.CancelledError:
# Something bad happens
raise ValueError('boom')

with pytest.raises(TimeoutError):
async with Nursery(timeout=0.5) as n:
n.start_soon(nasty())

await asyncio.sleep(0.1)
10 changes: 8 additions & 2 deletions traio/nursery.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ def cancel(self, exception: Exception = None):
and raise given Exception if needed.
:param exception: Exception to be raised
"""
assert self.state == State.STARTED, 'can only cancel a running nursery'
assert self.state != State.INIT, 'cannot cancel before even starting'

for task in self._pending_tasks:
if not task.done():
Expand Down Expand Up @@ -181,7 +181,13 @@ async def join(self):
# We may still have pending tasks if the Nursery is cancelled
for task in self._pending_tasks:
if not task.done():
await task.ensure_cancelled()
try:
await task.ensure_cancelled()
except Exception as ex: # pylint: disable=broad-except
# Too late for raising... and we need to move on cleaning other tasks!
self.logger.error(
'task `%s` failed to cancelled with exception: %s %s',
task, ex.__class__.__name__, ex)

def start_soon(
self, awaitable: Awaitable, *,
Expand Down

0 comments on commit 97f2b29

Please sign in to comment.