mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
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:
parent
624e3986fb
commit
0360e9f346
9 changed files with 106 additions and 17 deletions
|
@ -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.
|
||||||
|
|
|
@ -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:
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -228,14 +228,22 @@ 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)
|
||||||
|
|
||||||
f.cancel('my message')
|
with self.assertWarnsRegex(
|
||||||
|
DeprecationWarning,
|
||||||
|
"Passing 'msg' argument"
|
||||||
|
):
|
||||||
|
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)
|
||||||
self.assertEqual(f._cancel_message, 'my message')
|
self.assertEqual(f._cancel_message, 'my message')
|
||||||
|
|
||||||
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)
|
||||||
f.cancel('my message')
|
with self.assertWarnsRegex(
|
||||||
|
DeprecationWarning,
|
||||||
|
"Passing 'msg' argument"
|
||||||
|
):
|
||||||
|
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')
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -113,7 +113,11 @@ 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)
|
||||||
|
|
||||||
t.cancel('my message')
|
with self.assertWarnsRegex(
|
||||||
|
DeprecationWarning,
|
||||||
|
"Passing 'msg' argument"
|
||||||
|
):
|
||||||
|
t.cancel('my message')
|
||||||
self.assertEqual(t._cancel_message, 'my message')
|
self.assertEqual(t._cancel_message, 'my message')
|
||||||
|
|
||||||
with self.assertRaises(asyncio.CancelledError) as cm:
|
with self.assertRaises(asyncio.CancelledError) as cm:
|
||||||
|
@ -125,7 +129,11 @@ class BaseTaskTests:
|
||||||
async def coro():
|
async def coro():
|
||||||
pass
|
pass
|
||||||
t = self.new_task(self.loop, coro())
|
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'
|
t._cancel_message = 'my new message'
|
||||||
self.assertEqual(t._cancel_message, 'my new message')
|
self.assertEqual(t._cancel_message, 'my new message')
|
||||||
|
|
||||||
|
@ -582,7 +590,14 @@ 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)
|
||||||
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])
|
done, pending = await asyncio.wait([task])
|
||||||
task.result()
|
task.result()
|
||||||
|
|
||||||
|
@ -616,7 +631,14 @@ 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)
|
||||||
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])
|
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,7 +697,11 @@ 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.
|
||||||
task.cancel('my message')
|
with self.assertWarnsRegex(
|
||||||
|
DeprecationWarning,
|
||||||
|
"Passing 'msg' argument"
|
||||||
|
):
|
||||||
|
task.cancel('my message')
|
||||||
done, pending = await asyncio.wait([task])
|
done, pending = await asyncio.wait([task])
|
||||||
task.exception()
|
task.exception()
|
||||||
|
|
||||||
|
@ -2029,7 +2062,14 @@ 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)
|
||||||
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
|
await qwe
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Deprecate passing a message into :meth:`asyncio.Future.cancel` and
|
||||||
|
:meth:`asyncio.Task.cancel`
|
|
@ -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) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue