Skip to content

Commit

Permalink
bpo-30048: asyncio: fix Task.cancel() was ignored. (GH-1097)
Browse files Browse the repository at this point in the history
when there are no more `await` or `yield (from)` before return in coroutine,
cancel was ignored.

example:

    async def coro():
        asyncio.Task.current_task().cancel()
        return 42
    ...
    res = await coro()  # should raise CancelledError
  • Loading branch information
methane committed May 11, 2017
1 parent c475095 commit 991adca
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 1 deletion.
7 changes: 6 additions & 1 deletion Lib/asyncio/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,12 @@ def _step(self, exc=None):
else:
result = coro.throw(exc)
except StopIteration as exc:
self.set_result(exc.value)
if self._must_cancel:
# Task is cancelled right before coro stops.
self._must_cancel = False
self.set_exception(futures.CancelledError())
else:
self.set_result(exc.value)
except futures.CancelledError:
super().cancel() # I.e., Future.cancel(self).
except Exception as exc:
Expand Down
18 changes: 18 additions & 0 deletions Lib/test/test_asyncio/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,24 @@ def task():
self.assertFalse(t._must_cancel) # White-box test.
self.assertFalse(t.cancel())

def test_cancel_at_end(self):
"""coroutine end right after task is cancelled"""
loop = asyncio.new_event_loop()
self.set_event_loop(loop)

@asyncio.coroutine
def task():
t.cancel()
self.assertTrue(t._must_cancel) # White-box test.
return 12

t = self.new_task(loop, task())
self.assertRaises(
asyncio.CancelledError, loop.run_until_complete, t)
self.assertTrue(t.done())
self.assertFalse(t._must_cancel) # White-box test.
self.assertFalse(t.cancel())

def test_stop_while_run_in_complete(self):

def gen():
Expand Down
3 changes: 3 additions & 0 deletions Misc/NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,9 @@ Extension Modules
Library
-------

- bpo-30048: Fixed ``Task.cancel()`` can be ignored when the task is
running coroutine and the coroutine returned without any more ``await``.

- bpo-30298: Weaken the condition of deprecation warnings for inline modifiers.
Now allowed several subsequential inline modifiers at the start of the
pattern (e.g. ``'(?i)(?s)...'``). In verbose mode whitespaces and comments
Expand Down
12 changes: 12 additions & 0 deletions Modules/_asynciomodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -1985,6 +1985,16 @@ task_step_impl(TaskObj *task, PyObject *exc)
if (_PyGen_FetchStopIterationValue(&o) == 0) {
/* The error is StopIteration and that means that
the underlying coroutine has resolved */
if (task->task_must_cancel) {
// Task is cancelled right before coro stops.
Py_DECREF(o);
task->task_must_cancel = 0;
et = asyncio_CancelledError;
Py_INCREF(et);
ev = NULL;
tb = NULL;
goto set_exception;
}
PyObject *res = future_set_result((FutureObj*)task, o);
Py_DECREF(o);
if (res == NULL) {
Expand All @@ -2002,6 +2012,8 @@ task_step_impl(TaskObj *task, PyObject *exc)

/* Some other exception; pop it and call Task.set_exception() */
PyErr_Fetch(&et, &ev, &tb);

set_exception:
assert(et);
if (!ev || !PyObject_TypeCheck(ev, (PyTypeObject *) et)) {
PyErr_NormalizeException(&et, &ev, &tb);
Expand Down

0 comments on commit 991adca

Please sign in to comment.