Skip to content

Commit 435cf31

Browse files
committed
asyncio: preserve Task.cancel() msg through fut_waiter and CancelledError paths
1 parent 68ebc05 commit 435cf31

4 files changed

Lines changed: 29 additions & 2 deletions

File tree

Doc/whatsnew/3.16.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ asyncio
123123
which has been deprecated since Python 3.14.
124124
Use :func:`inspect.iscoroutinefunction` instead.
125125

126+
* :meth:`asyncio.Task.cancel` now preserves its *msg* argument when a task is
127+
cancelled while waiting on another future, so the message is retained when
128+
the task later raises :exc:`asyncio.CancelledError`.
129+
(Contributed by Prince Kumar in :gh:`NNNNN`.)
130+
126131
functools
127132
---------
128133

Lib/asyncio/tasks.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ def cancel(self, msg=None):
216216
# Leave self._fut_waiter; it may be a Task that
217217
# catches and ignores the cancellation so we may have
218218
# to cancel it again later.
219+
self._cancel_message = msg
219220
return True
220221
# It must be the case that self.__step is already scheduled.
221222
self._must_cancel = True
@@ -299,7 +300,7 @@ def __step_run_and_handle_result(self, exc):
299300
except exceptions.CancelledError as exc:
300301
# Save the original exception so we can chain it later.
301302
self._cancelled_exc = exc
302-
super().cancel() # I.e., Future.cancel(self).
303+
super().cancel(msg=self._cancel_message) # I.e., Future.cancel(self).
303304
except (KeyboardInterrupt, SystemExit) as exc:
304305
super().set_exception(exc)
305306
raise

Lib/test/test_asyncio/test_tasks.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1861,6 +1861,25 @@ async def coro():
18611861
self.assertIsNone(task._fut_waiter)
18621862
self.assertTrue(fut.cancelled())
18631863

1864+
def test_task_cancel_waiter_future_with_message(self):
1865+
fut = self.new_future(self.loop)
1866+
1867+
async def coro():
1868+
await fut
1869+
1870+
task = self.new_task(self.loop, coro())
1871+
test_utils.run_briefly(self.loop)
1872+
self.assertIs(task._fut_waiter, fut)
1873+
1874+
task.cancel('my message')
1875+
test_utils.run_briefly(self.loop)
1876+
with self.assertRaises(asyncio.CancelledError) as cm:
1877+
self.loop.run_until_complete(task)
1878+
self.assertEqual(cm.exception.args, ('my message',))
1879+
self.assertEqual(task._cancel_message, 'my message')
1880+
self.assertIsNone(task._fut_waiter)
1881+
self.assertTrue(fut.cancelled())
1882+
18641883
def test_task_set_methods(self):
18651884
async def notmuch():
18661885
return 'ko'

Modules/_asynciomodule.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2612,6 +2612,8 @@ _asyncio_Task_cancel_impl(TaskObj *self, PyObject *msg)
26122612
}
26132613

26142614
if (is_true) {
2615+
Py_XINCREF(msg);
2616+
Py_XSETREF(self->task_cancel_msg, msg);
26152617
Py_RETURN_TRUE;
26162618
}
26172619
}
@@ -3136,7 +3138,7 @@ task_step_impl(asyncio_state *state, TaskObj *task, PyObject *exc)
31363138
/* transfer ownership */
31373139
fut->fut_cancelled_exc = exc;
31383140

3139-
return future_cancel(state, fut, NULL);
3141+
return future_cancel(state, fut, fut->fut_cancel_msg);
31403142
}
31413143

31423144
/* Some other exception; pop it and call Task.set_exception() */

0 commit comments

Comments
 (0)