bpo-34872: Fix self-cancellation in C implementation of asyncio.Task (GH-9679)
The C implementation of asyncio.Task currently fails to perform the cancellation cleanup correctly in the following scenario. async def task1(): async def task2(): await task3 # task3 is never cancelled asyncio.current_task().cancel() await asyncio.create_task(task2()) The actuall error is a hardcoded call to `future_cancel()` instead of calling the `cancel()` method of a future-like object. Thanks to Vladimir Matveev for noticing the code discrepancy and to Yury Selivanov for coming up with a pathological scenario.
This commit is contained in:
parent
96c5932794
commit
0c797a6aca
|
@ -622,6 +622,42 @@ class BaseTaskTests:
|
|||
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():
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Fix self-cancellation in C implementation of asyncio.Task
|
|
@ -2713,14 +2713,19 @@ set_exception:
|
|||
|
||||
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;
|
||||
|
|
Loading…
Reference in New Issue