bpo-30048: asyncio: fix Task.cancel() was ignored. (GH-1546)
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
(cherry picked from commit 991adca012f5e106c2d4040ce619c696ba6f9c46)
diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py
index f91e70a..d7867d1 100644
--- a/Lib/asyncio/tasks.py
+++ b/Lib/asyncio/tasks.py
@@ -180,7 +180,12 @@
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 4f05319..5462c80 100644
--- a/Lib/test/test_asyncio/test_tasks.py
+++ b/Lib/test/test_asyncio/test_tasks.py
@@ -588,6 +588,24 @@
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():