summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorINADA Naoki <methane@users.noreply.github.com>2017-05-11 12:18:38 (GMT)
committerGitHub <noreply@github.com>2017-05-11 12:18:38 (GMT)
commit991adca012f5e106c2d4040ce619c696ba6f9c46 (patch)
tree90fa267454273f2e779ceffbeeae2ff84d5f01ca
parentc4750959acbfc3057f12aaec832483ba30898d1c (diff)
downloadcpython-991adca012f5e106c2d4040ce619c696ba6f9c46.zip
cpython-991adca012f5e106c2d4040ce619c696ba6f9c46.tar.gz
cpython-991adca012f5e106c2d4040ce619c696ba6f9c46.tar.bz2
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
-rw-r--r--Lib/asyncio/tasks.py7
-rw-r--r--Lib/test/test_asyncio/test_tasks.py18
-rw-r--r--Misc/NEWS3
-rw-r--r--Modules/_asynciomodule.c12
4 files changed, 39 insertions, 1 deletions
diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py
index 4fbcf3e..e453300 100644
--- a/Lib/asyncio/tasks.py
+++ b/Lib/asyncio/tasks.py
@@ -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:
diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py
index 754a675..988b288 100644
--- a/Lib/test/test_asyncio/test_tasks.py
+++ b/Lib/test/test_asyncio/test_tasks.py
@@ -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():
diff --git a/Misc/NEWS b/Misc/NEWS
index 5ee7ea6..4e17a66 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -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
diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c
index e902c04..150ca19 100644
--- a/Modules/_asynciomodule.c
+++ b/Modules/_asynciomodule.c
@@ -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);