Skip to content

Commit 991adca

Browse files
authored
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
1 parent c475095 commit 991adca

File tree

4 files changed

+39
-1
lines changed

4 files changed

+39
-1
lines changed

Lib/asyncio/tasks.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,12 @@ def _step(self, exc=None):
176176
else:
177177
result = coro.throw(exc)
178178
except StopIteration as exc:
179-
self.set_result(exc.value)
179+
if self._must_cancel:
180+
# Task is cancelled right before coro stops.
181+
self._must_cancel = False
182+
self.set_exception(futures.CancelledError())
183+
else:
184+
self.set_result(exc.value)
180185
except futures.CancelledError:
181186
super().cancel() # I.e., Future.cancel(self).
182187
except Exception as exc:

Lib/test/test_asyncio/test_tasks.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,24 @@ def task():
587587
self.assertFalse(t._must_cancel) # White-box test.
588588
self.assertFalse(t.cancel())
589589

590+
def test_cancel_at_end(self):
591+
"""coroutine end right after task is cancelled"""
592+
loop = asyncio.new_event_loop()
593+
self.set_event_loop(loop)
594+
595+
@asyncio.coroutine
596+
def task():
597+
t.cancel()
598+
self.assertTrue(t._must_cancel) # White-box test.
599+
return 12
600+
601+
t = self.new_task(loop, task())
602+
self.assertRaises(
603+
asyncio.CancelledError, loop.run_until_complete, t)
604+
self.assertTrue(t.done())
605+
self.assertFalse(t._must_cancel) # White-box test.
606+
self.assertFalse(t.cancel())
607+
590608
def test_stop_while_run_in_complete(self):
591609

592610
def gen():

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,9 @@ Extension Modules
320320
Library
321321
-------
322322

323+
- bpo-30048: Fixed ``Task.cancel()`` can be ignored when the task is
324+
running coroutine and the coroutine returned without any more ``await``.
325+
323326
- bpo-30298: Weaken the condition of deprecation warnings for inline modifiers.
324327
Now allowed several subsequential inline modifiers at the start of the
325328
pattern (e.g. ``'(?i)(?s)...'``). In verbose mode whitespaces and comments

Modules/_asynciomodule.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1985,6 +1985,16 @@ task_step_impl(TaskObj *task, PyObject *exc)
19851985
if (_PyGen_FetchStopIterationValue(&o) == 0) {
19861986
/* The error is StopIteration and that means that
19871987
the underlying coroutine has resolved */
1988+
if (task->task_must_cancel) {
1989+
// Task is cancelled right before coro stops.
1990+
Py_DECREF(o);
1991+
task->task_must_cancel = 0;
1992+
et = asyncio_CancelledError;
1993+
Py_INCREF(et);
1994+
ev = NULL;
1995+
tb = NULL;
1996+
goto set_exception;
1997+
}
19881998
PyObject *res = future_set_result((FutureObj*)task, o);
19891999
Py_DECREF(o);
19902000
if (res == NULL) {
@@ -2002,6 +2012,8 @@ task_step_impl(TaskObj *task, PyObject *exc)
20022012

20032013
/* Some other exception; pop it and call Task.set_exception() */
20042014
PyErr_Fetch(&et, &ev, &tb);
2015+
2016+
set_exception:
20052017
assert(et);
20062018
if (!ev || !PyObject_TypeCheck(ev, (PyTypeObject *) et)) {
20072019
PyErr_NormalizeException(&et, &ev, &tb);

0 commit comments

Comments
 (0)