bpo-30048: asyncio: fix Task.cancel() was ignored. (GH-1097)

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
This commit is contained in:
INADA Naoki 2017-05-11 21:18:38 +09:00 committed by GitHub
parent c4750959ac
commit 991adca012
4 changed files with 39 additions and 1 deletions

View File

@ -176,7 +176,12 @@ class Task(futures.Future):
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:

View File

@ -587,6 +587,24 @@ class BaseTaskTests:
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():

View File

@ -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

View File

@ -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) {
@ -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);