Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions Lib/test/test_asyncio/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,42 @@ def task():
self.assertFalse(t._must_cancel) # White-box test.
self.assertFalse(t.cancel())

def test_cancel_awaited_task(self):
# This tests for a relatively rare condition when
# a task cancellation is requested for a task which is not
# currently blocked, such as a task cancelling itself.
# In this situation we must ensure that whatever next future
# or task the cancelled task blocks on is cancelled correctly
# as well. See also bpo-34872.
loop = asyncio.new_event_loop()
self.addCleanup(lambda: loop.close())

task = nested_task = None
fut = self.new_future(loop)

async def nested():
await fut

async def coro():
nonlocal nested_task
# Create a sub-task and wait for it to run.
nested_task = self.new_task(loop, nested())
await asyncio.sleep(0)

# Request the current task to be cancelled.
task.cancel()
# Block on the nested task, which should be immediately
# cancelled.
await nested_task

task = self.new_task(loop, coro())
with self.assertRaises(asyncio.CancelledError):
loop.run_until_complete(task)

self.assertTrue(task.cancelled())
self.assertTrue(nested_task.cancelled())
self.assertTrue(fut.cancelled())

def test_stop_while_run_in_complete(self):

def gen():
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix self-cancellation in C implementation of asyncio.Task
11 changes: 8 additions & 3 deletions Modules/_asynciomodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -2662,14 +2662,19 @@ task_step_impl(TaskObj *task, PyObject *exc)

if (task->task_must_cancel) {
PyObject *r;
r = future_cancel(fut);
int is_true;
r = _PyObject_CallMethodId(fut, &PyId_cancel, NULL);
if (r == NULL) {
return NULL;
}
if (r == Py_True) {
is_true = PyObject_IsTrue(r);
Py_DECREF(r);
if (is_true < 0) {
return NULL;
}
else if (is_true) {
task->task_must_cancel = 0;
}
Py_DECREF(r);
}

Py_RETURN_NONE;
Expand Down