bpo-34872: Fix self-cancellation in C implementation of asyncio.Task · python/cpython@548ce9d (original) (raw)

3 files changed

lines changed

Original file line number Diff line number Diff line change
@@ -622,6 +622,42 @@ def task():
622 622 self.assertFalse(t._must_cancel) # White-box test.
623 623 self.assertFalse(t.cancel())
624 624
625 +def test_cancel_awaited_task(self):
626 +# This tests for a relatively rare condition when
627 +# a task cancellation is requested for a task which is not
628 +# currently blocked, such as a task cancelling itself.
629 +# In this situation we must ensure that whatever next future
630 +# or task the cancelled task blocks on is cancelled correctly
631 +# as well. See also bpo-34872.
632 +loop = asyncio.new_event_loop()
633 +self.addCleanup(lambda: loop.close())
634 +
635 +task = nested_task = None
636 +fut = self.new_future(loop)
637 +
638 +async def nested():
639 +await fut
640 +
641 +async def coro():
642 +nonlocal nested_task
643 +# Create a sub-task and wait for it to run.
644 +nested_task = self.new_task(loop, nested())
645 +await asyncio.sleep(0)
646 +
647 +# Request the current task to be cancelled.
648 +task.cancel()
649 +# Block on the nested task, which should be immediately
650 +# cancelled.
651 +await nested_task
652 +
653 +task = self.new_task(loop, coro())
654 +with self.assertRaises(asyncio.CancelledError):
655 +loop.run_until_complete(task)
656 +
657 +self.assertTrue(task.cancelled())
658 +self.assertTrue(nested_task.cancelled())
659 +self.assertTrue(fut.cancelled())
660 +
625 661 def test_stop_while_run_in_complete(self):
626 662
627 663 def gen():
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1 +Fix self-cancellation in C implementation of asyncio.Task
Original file line number Diff line number Diff line change
@@ -2713,14 +2713,19 @@ task_step_impl(TaskObj *task, PyObject *exc)
2713 2713
2714 2714 if (task->task_must_cancel) {
2715 2715 PyObject *r;
2716 -r = future_cancel(fut);
2716 +int is_true;
2717 +r = _PyObject_CallMethodId(fut, &PyId_cancel, NULL);
2717 2718 if (r == NULL) {
2718 2719 return NULL;
2719 2720 }
2720 -if (r == Py_True) {
2721 +is_true = PyObject_IsTrue(r);
2722 +Py_DECREF(r);
2723 +if (is_true < 0) {
2724 +return NULL;
2725 + }
2726 +else if (is_true) {
2721 2727 task->task_must_cancel = 0;
2722 2728 }
2723 -Py_DECREF(r);
2724 2729 }
2725 2730
2726 2731 Py_RETURN_NONE;