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:
parent
c4750959ac
commit
991adca012
|
@ -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:
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue