From 3923c11294f96c7694f5fa1ac07506fa0a2384c3 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sat, 12 Mar 2022 21:08:10 +0200 Subject: [PATCH 1/5] bpo-46829: Deprecate passing a message into Future.cancel() and Task.cancel() --- Lib/asyncio/futures.py | 6 +++ Lib/asyncio/tasks.py | 5 ++ Lib/test/test_asyncio/test_futures.py | 12 ++++- Lib/test/test_asyncio/test_taskgroups.py | 12 ++++- Lib/test/test_asyncio/test_tasks.py | 54 ++++++++++++++++--- .../2022-03-12-21-07-21.bpo-46829.cpGoPV.rst | 2 + Modules/_asynciomodule.c | 20 +++++++ 7 files changed, 100 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2022-03-12-21-07-21.bpo-46829.cpGoPV.rst 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 059143fb9086fb..cd80a312e2d84e 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -201,6 +201,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..31a6fae210c4a7 100644 --- a/Lib/test/test_asyncio/test_taskgroups.py +++ b/Lib/test/test_asyncio/test_taskgroups.py @@ -190,7 +190,11 @@ async def runner(): await asyncio.sleep(0.1) self.assertFalse(r.done()) - r.cancel("test") + with self.assertWarnsRegex( + DeprecationWarning, + "Passing 'msg' argument" + ): + r.cancel("test") with self.assertRaises(asyncio.CancelledError) as cm: await r @@ -252,7 +256,11 @@ async def runner(): await asyncio.sleep(0.1) self.assertFalse(r.done()) - r.cancel("test") + with self.assertWarnsRegex( + DeprecationWarning, + "Passing 'msg' argument" + ): + r.cancel("test") with self.assertRaises(asyncio.CancelledError) as cm: await r diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index 950879204e703b..57b1cb220aac86 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -121,7 +121,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: @@ -133,7 +137,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') @@ -590,7 +598,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() @@ -624,7 +639,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() @@ -647,10 +669,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: @@ -676,7 +705,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() @@ -2326,7 +2359,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..b1fc1dd7037e6c --- /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 to :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) { From 55b65f31ba917b36150f41c23e80d748a1d26a3f Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sat, 12 Mar 2022 21:09:20 +0200 Subject: [PATCH 2/5] Update 2022-03-12-21-07-21.bpo-46829.cpGoPV.rst --- .../Core and Builtins/2022-03-12-21-07-21.bpo-46829.cpGoPV.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index b1fc1dd7037e6c..9d260f5b1dddbe 100644 --- 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 @@ -1,2 +1,2 @@ -Deprecate passing a message to :meth:`asyncio.Future.cancel` and +Deprecate passing a message into :meth:`asyncio.Future.cancel` and :meth:`asyncio.Task.cancel` From 55b43fd98418ad7b763df13f86ed6c87c8c6ad79 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sat, 12 Mar 2022 22:15:31 +0200 Subject: [PATCH 3/5] Fix notes --- Lib/test/test_asyncio/test_taskgroups.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Lib/test/test_asyncio/test_taskgroups.py b/Lib/test/test_asyncio/test_taskgroups.py index 31a6fae210c4a7..4c8c07bf0d92f6 100644 --- a/Lib/test/test_asyncio/test_taskgroups.py +++ b/Lib/test/test_asyncio/test_taskgroups.py @@ -190,16 +190,10 @@ async def runner(): await asyncio.sleep(0.1) self.assertFalse(r.done()) - with self.assertWarnsRegex( - DeprecationWarning, - "Passing 'msg' argument" - ): - 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): From 3f89aa26e049a1fdded7f7740fcdbf67928ce41c Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sat, 12 Mar 2022 23:27:50 +0200 Subject: [PATCH 4/5] Drop another warning test --- Lib/test/test_asyncio/test_taskgroups.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Lib/test/test_asyncio/test_taskgroups.py b/Lib/test/test_asyncio/test_taskgroups.py index 4c8c07bf0d92f6..1bfb5fda5bbe09 100644 --- a/Lib/test/test_asyncio/test_taskgroups.py +++ b/Lib/test/test_asyncio/test_taskgroups.py @@ -250,16 +250,10 @@ async def runner(): await asyncio.sleep(0.1) self.assertFalse(r.done()) - with self.assertWarnsRegex( - DeprecationWarning, - "Passing 'msg' argument" - ): - 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 From ae50f3e64cca29bd1317ab80d043ae21a5aa3a6a Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Mon, 14 Mar 2022 01:56:19 +0200 Subject: [PATCH 5/5] Update docs --- Doc/library/asyncio-future.rst | 10 ++++++++++ Doc/library/asyncio-task.rst | 6 ++++-- 2 files changed, 14 insertions(+), 2 deletions(-) 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: