diff options
author | Andrew Svetlov <andrew.svetlov@gmail.com> | 2022-03-23 15:43:05 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-03-23 15:43:05 (GMT) |
commit | 0360e9f34659e7d7f3dae021b82f78452db8c714 (patch) | |
tree | f88f12dd56fb3f0cca5557447835c94fbea12cb5 | |
parent | 624e3986fbf8467772e4863b7ec004e65adff619 (diff) | |
download | cpython-0360e9f34659e7d7f3dae021b82f78452db8c714.zip cpython-0360e9f34659e7d7f3dae021b82f78452db8c714.tar.gz cpython-0360e9f34659e7d7f3dae021b82f78452db8c714.tar.bz2 |
bpo-46829: Deprecate passing a message into Future.cancel() and Task.cancel() (GH-31840)
After a long deliberation we ended up feeling that the message argument for Future.cancel(), added in 3.9, was a bad idea, so we're deprecating it in 3.11 and plan to remove it in 3.13.
-rw-r--r-- | Doc/library/asyncio-future.rst | 10 | ||||
-rw-r--r-- | Doc/library/asyncio-task.rst | 6 | ||||
-rw-r--r-- | Lib/asyncio/futures.py | 6 | ||||
-rw-r--r-- | Lib/asyncio/tasks.py | 5 | ||||
-rw-r--r-- | Lib/test/test_asyncio/test_futures.py | 12 | ||||
-rw-r--r-- | Lib/test/test_asyncio/test_taskgroups.py | 8 | ||||
-rw-r--r-- | Lib/test/test_asyncio/test_tasks.py | 54 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Core and Builtins/2022-03-12-21-07-21.bpo-46829.cpGoPV.rst | 2 | ||||
-rw-r--r-- | Modules/_asynciomodule.c | 20 |
9 files changed, 106 insertions, 17 deletions
diff --git a/Doc/library/asyncio-future.rst b/Doc/library/asyncio-future.rst index 7426e82..f74f2e6 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 294f5ab..21a4cb582 100644 --- a/Doc/library/asyncio-task.rst +++ b/Doc/library/asyncio-task.rst @@ -810,8 +810,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 bfd4ee9..08c79e7 100644 --- a/Lib/asyncio/futures.py +++ b/Lib/asyncio/futures.py @@ -8,6 +8,7 @@ import concurrent.futures import contextvars import logging import sys +import warnings from types import GenericAlias from . import base_futures @@ -150,6 +151,11 @@ class Future: 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 e876f8d..27fe58d 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -207,6 +207,11 @@ class Task(futures._PyFuture): # Inherit Python Task implementation 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 84d7d45..cf677f6 100644 --- a/Lib/test/test_asyncio/test_futures.py +++ b/Lib/test/test_asyncio/test_futures.py @@ -228,14 +228,22 @@ class BaseFutureTests: 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 dea5d6d..69369a6 100644 --- a/Lib/test/test_asyncio/test_taskgroups.py +++ b/Lib/test/test_asyncio/test_taskgroups.py @@ -191,12 +191,10 @@ class TestTaskGroup(unittest.IsolatedAsyncioTestCase): 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): @@ -253,12 +251,10 @@ class TestTaskGroup(unittest.IsolatedAsyncioTestCase): 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 b86646e..8df1957 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -113,7 +113,11 @@ class BaseTaskTests: 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: @@ -125,7 +129,11 @@ class BaseTaskTests: 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') @@ -582,7 +590,14 @@ class BaseTaskTests: 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() @@ -616,7 +631,14 @@ class BaseTaskTests: 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() @@ -639,10 +661,17 @@ class BaseTaskTests: 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: @@ -668,7 +697,11 @@ class BaseTaskTests: 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() @@ -2029,7 +2062,14 @@ class BaseTaskTests: 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 0000000..9d260f5 --- /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 c3e9cb2..5b8555e 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); } @@ -2176,6 +2186,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) { |