diff --git a/Doc/library/asyncio-future.rst b/Doc/library/asyncio-future.rst index 7426e8291e1424..f74f2e6f8935ea 100644 --- a/Doc/library/asyncio-future.rst +++ b/Doc/library/asyncio-future.rst @@ -196,6 +196,11 @@ Future Object .. versionchanged:: 3.9 Added the *msg* parameter. + .. deprecated-removed:: 3.11 3.14 + *msg* parameter is ambiguous when multiple :meth:`cancel` + are called with different cancellation messages. + The argument will be removed. + .. method:: exception() Return the exception that was set on this Future. @@ -276,3 +281,8 @@ the Future has a result:: - :meth:`asyncio.Future.cancel` accepts an optional ``msg`` argument, but :func:`concurrent.futures.cancel` does not. + + .. deprecated-removed:: 3.11 3.14 + *msg* parameter is ambiguous when multiple :meth:`cancel` + are called with different cancellation messages. + The argument will be removed. diff --git a/Doc/library/asyncio-task.rst b/Doc/library/asyncio-task.rst index b30b2894277a2a..eda915d1929c38 100644 --- a/Doc/library/asyncio-task.rst +++ b/Doc/library/asyncio-task.rst @@ -843,8 +843,10 @@ Task Object .. versionchanged:: 3.9 Added the *msg* parameter. - .. versionchanged:: 3.11 - The ``msg`` parameter is propagated from cancelled task to its awaiter. + .. deprecated-removed:: 3.11 3.14 + *msg* parameter is ambiguous when multiple :meth:`cancel` + are called with different cancellation messages. + The argument will be removed. .. _asyncio_example_task_cancel: diff --git a/Lib/asyncio/futures.py b/Lib/asyncio/futures.py index 5a92f1769fa9f2..f8b7b3f5326672 100644 --- a/Lib/asyncio/futures.py +++ b/Lib/asyncio/futures.py @@ -8,6 +8,7 @@ import contextvars import logging import sys +import warnings from types import GenericAlias from . import base_futures @@ -153,6 +154,11 @@ def cancel(self, msg=None): change the future's state to cancelled, schedule the callbacks and return True. """ + if msg is not None: + warnings.warn("Passing 'msg' argument to Future.cancel() " + "is deprecated since Python 3.11, and " + "scheduled for removal in Python 3.14.", + DeprecationWarning, stacklevel=2) self.__log_traceback = False if self._state != _PENDING: return False diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index e604298e5efc01..1dc1b46bf397a4 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -204,6 +204,11 @@ def cancel(self, msg=None): This also increases the task's count of cancellation requests. """ + if msg is not None: + warnings.warn("Passing 'msg' argument to Task.cancel() " + "is deprecated since Python 3.11, and " + "scheduled for removal in Python 3.14.", + DeprecationWarning, stacklevel=2) self._log_traceback = False if self.done(): return False diff --git a/Lib/test/test_asyncio/test_futures.py b/Lib/test/test_asyncio/test_futures.py index 84d7d45af949ef..cf677f6a951155 100644 --- a/Lib/test/test_asyncio/test_futures.py +++ b/Lib/test/test_asyncio/test_futures.py @@ -228,14 +228,22 @@ def test_future_cancel_message_getter(self): self.assertTrue(hasattr(f, '_cancel_message')) self.assertEqual(f._cancel_message, None) - f.cancel('my message') + with self.assertWarnsRegex( + DeprecationWarning, + "Passing 'msg' argument" + ): + f.cancel('my message') with self.assertRaises(asyncio.CancelledError): self.loop.run_until_complete(f) self.assertEqual(f._cancel_message, 'my message') def test_future_cancel_message_setter(self): f = self._new_future(loop=self.loop) - f.cancel('my message') + with self.assertWarnsRegex( + DeprecationWarning, + "Passing 'msg' argument" + ): + f.cancel('my message') f._cancel_message = 'my new message' self.assertEqual(f._cancel_message, 'my new message') diff --git a/Lib/test/test_asyncio/test_taskgroups.py b/Lib/test/test_asyncio/test_taskgroups.py index df51528e107939..1bfb5fda5bbe09 100644 --- a/Lib/test/test_asyncio/test_taskgroups.py +++ b/Lib/test/test_asyncio/test_taskgroups.py @@ -190,12 +190,10 @@ async def runner(): await asyncio.sleep(0.1) self.assertFalse(r.done()) - r.cancel("test") + r.cancel() with self.assertRaises(asyncio.CancelledError) as cm: await r - self.assertEqual(cm.exception.args, ('test',)) - self.assertEqual(NUM, 5) async def test_taskgroup_07(self): @@ -252,12 +250,10 @@ async def runner(): await asyncio.sleep(0.1) self.assertFalse(r.done()) - r.cancel("test") + r.cancel() with self.assertRaises(asyncio.CancelledError) as cm: await r - self.assertEqual(cm.exception.args, ('test',)) - async def test_taskgroup_09(self): t1 = t2 = None diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index 95fabf728818bb..0ea61c6de8f78e 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -120,7 +120,11 @@ async def coro(): self.assertTrue(hasattr(t, '_cancel_message')) self.assertEqual(t._cancel_message, None) - t.cancel('my message') + with self.assertWarnsRegex( + DeprecationWarning, + "Passing 'msg' argument" + ): + t.cancel('my message') self.assertEqual(t._cancel_message, 'my message') with self.assertRaises(asyncio.CancelledError) as cm: @@ -132,7 +136,11 @@ def test_task_cancel_message_setter(self): async def coro(): pass t = self.new_task(self.loop, coro()) - t.cancel('my message') + with self.assertWarnsRegex( + DeprecationWarning, + "Passing 'msg' argument" + ): + t.cancel('my message') t._cancel_message = 'my new message' self.assertEqual(t._cancel_message, 'my new message') @@ -589,7 +597,14 @@ async def sleep(): async def coro(): task = self.new_task(loop, sleep()) await asyncio.sleep(0) - task.cancel(*cancel_args) + if cancel_args not in ((), (None,)): + with self.assertWarnsRegex( + DeprecationWarning, + "Passing 'msg' argument" + ): + task.cancel(*cancel_args) + else: + task.cancel(*cancel_args) done, pending = await asyncio.wait([task]) task.result() @@ -623,7 +638,14 @@ async def sleep(): async def coro(): task = self.new_task(loop, sleep()) await asyncio.sleep(0) - task.cancel(*cancel_args) + if cancel_args not in ((), (None,)): + with self.assertWarnsRegex( + DeprecationWarning, + "Passing 'msg' argument" + ): + task.cancel(*cancel_args) + else: + task.cancel(*cancel_args) done, pending = await asyncio.wait([task]) task.exception() @@ -646,10 +668,17 @@ async def sleep(): fut.set_result(None) await asyncio.sleep(10) + def cancel(task, msg): + with self.assertWarnsRegex( + DeprecationWarning, + "Passing 'msg' argument" + ): + task.cancel(msg) + async def coro(): inner_task = self.new_task(loop, sleep()) await fut - loop.call_soon(inner_task.cancel, 'msg') + loop.call_soon(cancel, inner_task, 'msg') try: await inner_task except asyncio.CancelledError as ex: @@ -675,7 +704,11 @@ async def sleep(): async def coro(): task = self.new_task(loop, sleep()) # We deliberately leave out the sleep here. - task.cancel('my message') + with self.assertWarnsRegex( + DeprecationWarning, + "Passing 'msg' argument" + ): + task.cancel('my message') done, pending = await asyncio.wait([task]) task.exception() @@ -2325,7 +2358,14 @@ async def test(): async def main(): qwe = self.new_task(loop, test()) await asyncio.sleep(0.2) - qwe.cancel(*cancel_args) + if cancel_args not in ((), (None,)): + with self.assertWarnsRegex( + DeprecationWarning, + "Passing 'msg' argument" + ): + qwe.cancel(*cancel_args) + else: + qwe.cancel(*cancel_args) await qwe try: diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-03-12-21-07-21.bpo-46829.cpGoPV.rst b/Misc/NEWS.d/next/Core and Builtins/2022-03-12-21-07-21.bpo-46829.cpGoPV.rst new file mode 100644 index 00000000000000..9d260f5b1dddbe --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2022-03-12-21-07-21.bpo-46829.cpGoPV.rst @@ -0,0 +1,2 @@ +Deprecate passing a message into :meth:`asyncio.Future.cancel` and +:meth:`asyncio.Task.cancel` diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 2a6c0b335ccfb0..faa567ab92015d 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -1096,6 +1096,16 @@ static PyObject * _asyncio_Future_cancel_impl(FutureObj *self, PyObject *msg) /*[clinic end generated code: output=3edebbc668e5aba3 input=925eb545251f2c5a]*/ { + if (msg != Py_None) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Passing 'msg' argument to Future.cancel() " + "is deprecated since Python 3.11, and " + "scheduled for removal in Python 3.14.", + 2)) + { + return NULL; + } + } ENSURE_FUTURE_ALIVE(self) return future_cancel(self, msg); } @@ -2199,6 +2209,16 @@ static PyObject * _asyncio_Task_cancel_impl(TaskObj *self, PyObject *msg) /*[clinic end generated code: output=c66b60d41c74f9f1 input=7bb51bf25974c783]*/ { + if (msg != Py_None) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Passing 'msg' argument to Task.cancel() " + "is deprecated since Python 3.11, and " + "scheduled for removal in Python 3.14.", + 2)) + { + return NULL; + } + } self->task_log_tb = 0; if (self->task_state != STATE_PENDING) {