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.
This commit is contained in:
Andrew Svetlov 2022-03-23 17:43:05 +02:00 committed by GitHub
parent 624e3986fb
commit 0360e9f346
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 106 additions and 17 deletions

View file

@ -196,6 +196,11 @@ Future Object
.. versionchanged:: 3.9 .. versionchanged:: 3.9
Added the *msg* parameter. 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() .. method:: exception()
Return the exception that was set on this Future. 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, - :meth:`asyncio.Future.cancel` accepts an optional ``msg`` argument,
but :func:`concurrent.futures.cancel` does not. 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.

View file

@ -810,8 +810,10 @@ Task Object
.. versionchanged:: 3.9 .. versionchanged:: 3.9
Added the *msg* parameter. Added the *msg* parameter.
.. versionchanged:: 3.11 .. deprecated-removed:: 3.11 3.14
The ``msg`` parameter is propagated from cancelled task to its awaiter. *msg* parameter is ambiguous when multiple :meth:`cancel`
are called with different cancellation messages.
The argument will be removed.
.. _asyncio_example_task_cancel: .. _asyncio_example_task_cancel:

View file

@ -8,6 +8,7 @@ import concurrent.futures
import contextvars import contextvars
import logging import logging
import sys import sys
import warnings
from types import GenericAlias from types import GenericAlias
from . import base_futures from . import base_futures
@ -150,6 +151,11 @@ class Future:
change the future's state to cancelled, schedule the callbacks and change the future's state to cancelled, schedule the callbacks and
return True. 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 self.__log_traceback = False
if self._state != _PENDING: if self._state != _PENDING:
return False return False

View file

@ -207,6 +207,11 @@ class Task(futures._PyFuture): # Inherit Python Task implementation
This also increases the task's count of cancellation requests. 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 self._log_traceback = False
if self.done(): if self.done():
return False return False

View file

@ -228,6 +228,10 @@ class BaseFutureTests:
self.assertTrue(hasattr(f, '_cancel_message')) self.assertTrue(hasattr(f, '_cancel_message'))
self.assertEqual(f._cancel_message, None) self.assertEqual(f._cancel_message, None)
with self.assertWarnsRegex(
DeprecationWarning,
"Passing 'msg' argument"
):
f.cancel('my message') f.cancel('my message')
with self.assertRaises(asyncio.CancelledError): with self.assertRaises(asyncio.CancelledError):
self.loop.run_until_complete(f) self.loop.run_until_complete(f)
@ -235,6 +239,10 @@ class BaseFutureTests:
def test_future_cancel_message_setter(self): def test_future_cancel_message_setter(self):
f = self._new_future(loop=self.loop) f = self._new_future(loop=self.loop)
with self.assertWarnsRegex(
DeprecationWarning,
"Passing 'msg' argument"
):
f.cancel('my message') f.cancel('my message')
f._cancel_message = 'my new message' f._cancel_message = 'my new message'
self.assertEqual(f._cancel_message, 'my new message') self.assertEqual(f._cancel_message, 'my new message')

View file

@ -191,12 +191,10 @@ class TestTaskGroup(unittest.IsolatedAsyncioTestCase):
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
self.assertFalse(r.done()) self.assertFalse(r.done())
r.cancel("test") r.cancel()
with self.assertRaises(asyncio.CancelledError) as cm: with self.assertRaises(asyncio.CancelledError) as cm:
await r await r
self.assertEqual(cm.exception.args, ('test',))
self.assertEqual(NUM, 5) self.assertEqual(NUM, 5)
async def test_taskgroup_07(self): async def test_taskgroup_07(self):
@ -253,12 +251,10 @@ class TestTaskGroup(unittest.IsolatedAsyncioTestCase):
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
self.assertFalse(r.done()) self.assertFalse(r.done())
r.cancel("test") r.cancel()
with self.assertRaises(asyncio.CancelledError) as cm: with self.assertRaises(asyncio.CancelledError) as cm:
await r await r
self.assertEqual(cm.exception.args, ('test',))
async def test_taskgroup_09(self): async def test_taskgroup_09(self):
t1 = t2 = None t1 = t2 = None

View file

@ -113,6 +113,10 @@ class BaseTaskTests:
self.assertTrue(hasattr(t, '_cancel_message')) self.assertTrue(hasattr(t, '_cancel_message'))
self.assertEqual(t._cancel_message, None) self.assertEqual(t._cancel_message, None)
with self.assertWarnsRegex(
DeprecationWarning,
"Passing 'msg' argument"
):
t.cancel('my message') t.cancel('my message')
self.assertEqual(t._cancel_message, 'my message') self.assertEqual(t._cancel_message, 'my message')
@ -125,6 +129,10 @@ class BaseTaskTests:
async def coro(): async def coro():
pass pass
t = self.new_task(self.loop, coro()) t = self.new_task(self.loop, coro())
with self.assertWarnsRegex(
DeprecationWarning,
"Passing 'msg' argument"
):
t.cancel('my message') t.cancel('my message')
t._cancel_message = 'my new message' t._cancel_message = 'my new message'
self.assertEqual(t._cancel_message, 'my new message') self.assertEqual(t._cancel_message, 'my new message')
@ -582,6 +590,13 @@ class BaseTaskTests:
async def coro(): async def coro():
task = self.new_task(loop, sleep()) task = self.new_task(loop, sleep())
await asyncio.sleep(0) await asyncio.sleep(0)
if cancel_args not in ((), (None,)):
with self.assertWarnsRegex(
DeprecationWarning,
"Passing 'msg' argument"
):
task.cancel(*cancel_args)
else:
task.cancel(*cancel_args) task.cancel(*cancel_args)
done, pending = await asyncio.wait([task]) done, pending = await asyncio.wait([task])
task.result() task.result()
@ -616,6 +631,13 @@ class BaseTaskTests:
async def coro(): async def coro():
task = self.new_task(loop, sleep()) task = self.new_task(loop, sleep())
await asyncio.sleep(0) await asyncio.sleep(0)
if cancel_args not in ((), (None,)):
with self.assertWarnsRegex(
DeprecationWarning,
"Passing 'msg' argument"
):
task.cancel(*cancel_args)
else:
task.cancel(*cancel_args) task.cancel(*cancel_args)
done, pending = await asyncio.wait([task]) done, pending = await asyncio.wait([task])
task.exception() task.exception()
@ -639,10 +661,17 @@ class BaseTaskTests:
fut.set_result(None) fut.set_result(None)
await asyncio.sleep(10) await asyncio.sleep(10)
def cancel(task, msg):
with self.assertWarnsRegex(
DeprecationWarning,
"Passing 'msg' argument"
):
task.cancel(msg)
async def coro(): async def coro():
inner_task = self.new_task(loop, sleep()) inner_task = self.new_task(loop, sleep())
await fut await fut
loop.call_soon(inner_task.cancel, 'msg') loop.call_soon(cancel, inner_task, 'msg')
try: try:
await inner_task await inner_task
except asyncio.CancelledError as ex: except asyncio.CancelledError as ex:
@ -668,6 +697,10 @@ class BaseTaskTests:
async def coro(): async def coro():
task = self.new_task(loop, sleep()) task = self.new_task(loop, sleep())
# We deliberately leave out the sleep here. # We deliberately leave out the sleep here.
with self.assertWarnsRegex(
DeprecationWarning,
"Passing 'msg' argument"
):
task.cancel('my message') task.cancel('my message')
done, pending = await asyncio.wait([task]) done, pending = await asyncio.wait([task])
task.exception() task.exception()
@ -2029,6 +2062,13 @@ class BaseTaskTests:
async def main(): async def main():
qwe = self.new_task(loop, test()) qwe = self.new_task(loop, test())
await asyncio.sleep(0.2) await asyncio.sleep(0.2)
if cancel_args not in ((), (None,)):
with self.assertWarnsRegex(
DeprecationWarning,
"Passing 'msg' argument"
):
qwe.cancel(*cancel_args)
else:
qwe.cancel(*cancel_args) qwe.cancel(*cancel_args)
await qwe await qwe

View file

@ -0,0 +1,2 @@
Deprecate passing a message into :meth:`asyncio.Future.cancel` and
:meth:`asyncio.Task.cancel`

View file

@ -1096,6 +1096,16 @@ static PyObject *
_asyncio_Future_cancel_impl(FutureObj *self, PyObject *msg) _asyncio_Future_cancel_impl(FutureObj *self, PyObject *msg)
/*[clinic end generated code: output=3edebbc668e5aba3 input=925eb545251f2c5a]*/ /*[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) ENSURE_FUTURE_ALIVE(self)
return future_cancel(self, msg); return future_cancel(self, msg);
} }
@ -2176,6 +2186,16 @@ static PyObject *
_asyncio_Task_cancel_impl(TaskObj *self, PyObject *msg) _asyncio_Task_cancel_impl(TaskObj *self, PyObject *msg)
/*[clinic end generated code: output=c66b60d41c74f9f1 input=7bb51bf25974c783]*/ /*[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; self->task_log_tb = 0;
if (self->task_state != STATE_PENDING) { if (self->task_state != STATE_PENDING) {