Skip to content

Commit 0360e9f

Browse files
authored
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.
1 parent 624e398 commit 0360e9f

File tree

9 files changed

+106
-17
lines changed

9 files changed

+106
-17
lines changed

Doc/library/asyncio-future.rst

+10
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,11 @@ Future Object
196196
.. versionchanged:: 3.9
197197
Added the *msg* parameter.
198198

199+
.. deprecated-removed:: 3.11 3.14
200+
*msg* parameter is ambiguous when multiple :meth:`cancel`
201+
are called with different cancellation messages.
202+
The argument will be removed.
203+
199204
.. method:: exception()
200205

201206
Return the exception that was set on this Future.
@@ -276,3 +281,8 @@ the Future has a result::
276281

277282
- :meth:`asyncio.Future.cancel` accepts an optional ``msg`` argument,
278283
but :func:`concurrent.futures.cancel` does not.
284+
285+
.. deprecated-removed:: 3.11 3.14
286+
*msg* parameter is ambiguous when multiple :meth:`cancel`
287+
are called with different cancellation messages.
288+
The argument will be removed.

Doc/library/asyncio-task.rst

+4-2
Original file line numberDiff line numberDiff line change
@@ -810,8 +810,10 @@ Task Object
810810
.. versionchanged:: 3.9
811811
Added the *msg* parameter.
812812

813-
.. versionchanged:: 3.11
814-
The ``msg`` parameter is propagated from cancelled task to its awaiter.
813+
.. deprecated-removed:: 3.11 3.14
814+
*msg* parameter is ambiguous when multiple :meth:`cancel`
815+
are called with different cancellation messages.
816+
The argument will be removed.
815817

816818
.. _asyncio_example_task_cancel:
817819

Lib/asyncio/futures.py

+6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import contextvars
99
import logging
1010
import sys
11+
import warnings
1112
from types import GenericAlias
1213

1314
from . import base_futures
@@ -150,6 +151,11 @@ def cancel(self, msg=None):
150151
change the future's state to cancelled, schedule the callbacks and
151152
return True.
152153
"""
154+
if msg is not None:
155+
warnings.warn("Passing 'msg' argument to Future.cancel() "
156+
"is deprecated since Python 3.11, and "
157+
"scheduled for removal in Python 3.14.",
158+
DeprecationWarning, stacklevel=2)
153159
self.__log_traceback = False
154160
if self._state != _PENDING:
155161
return False

Lib/asyncio/tasks.py

+5
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,11 @@ def cancel(self, msg=None):
207207
208208
This also increases the task's count of cancellation requests.
209209
"""
210+
if msg is not None:
211+
warnings.warn("Passing 'msg' argument to Task.cancel() "
212+
"is deprecated since Python 3.11, and "
213+
"scheduled for removal in Python 3.14.",
214+
DeprecationWarning, stacklevel=2)
210215
self._log_traceback = False
211216
if self.done():
212217
return False

Lib/test/test_asyncio/test_futures.py

+10-2
Original file line numberDiff line numberDiff line change
@@ -228,14 +228,22 @@ def test_future_cancel_message_getter(self):
228228
self.assertTrue(hasattr(f, '_cancel_message'))
229229
self.assertEqual(f._cancel_message, None)
230230

231-
f.cancel('my message')
231+
with self.assertWarnsRegex(
232+
DeprecationWarning,
233+
"Passing 'msg' argument"
234+
):
235+
f.cancel('my message')
232236
with self.assertRaises(asyncio.CancelledError):
233237
self.loop.run_until_complete(f)
234238
self.assertEqual(f._cancel_message, 'my message')
235239

236240
def test_future_cancel_message_setter(self):
237241
f = self._new_future(loop=self.loop)
238-
f.cancel('my message')
242+
with self.assertWarnsRegex(
243+
DeprecationWarning,
244+
"Passing 'msg' argument"
245+
):
246+
f.cancel('my message')
239247
f._cancel_message = 'my new message'
240248
self.assertEqual(f._cancel_message, 'my new message')
241249

Lib/test/test_asyncio/test_taskgroups.py

+2-6
Original file line numberDiff line numberDiff line change
@@ -191,12 +191,10 @@ async def runner():
191191
await asyncio.sleep(0.1)
192192

193193
self.assertFalse(r.done())
194-
r.cancel("test")
194+
r.cancel()
195195
with self.assertRaises(asyncio.CancelledError) as cm:
196196
await r
197197

198-
self.assertEqual(cm.exception.args, ('test',))
199-
200198
self.assertEqual(NUM, 5)
201199

202200
async def test_taskgroup_07(self):
@@ -253,12 +251,10 @@ async def runner():
253251
await asyncio.sleep(0.1)
254252

255253
self.assertFalse(r.done())
256-
r.cancel("test")
254+
r.cancel()
257255
with self.assertRaises(asyncio.CancelledError) as cm:
258256
await r
259257

260-
self.assertEqual(cm.exception.args, ('test',))
261-
262258
async def test_taskgroup_09(self):
263259

264260
t1 = t2 = None

Lib/test/test_asyncio/test_tasks.py

+47-7
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,11 @@ async def coro():
113113
self.assertTrue(hasattr(t, '_cancel_message'))
114114
self.assertEqual(t._cancel_message, None)
115115

116-
t.cancel('my message')
116+
with self.assertWarnsRegex(
117+
DeprecationWarning,
118+
"Passing 'msg' argument"
119+
):
120+
t.cancel('my message')
117121
self.assertEqual(t._cancel_message, 'my message')
118122

119123
with self.assertRaises(asyncio.CancelledError) as cm:
@@ -125,7 +129,11 @@ def test_task_cancel_message_setter(self):
125129
async def coro():
126130
pass
127131
t = self.new_task(self.loop, coro())
128-
t.cancel('my message')
132+
with self.assertWarnsRegex(
133+
DeprecationWarning,
134+
"Passing 'msg' argument"
135+
):
136+
t.cancel('my message')
129137
t._cancel_message = 'my new message'
130138
self.assertEqual(t._cancel_message, 'my new message')
131139

@@ -582,7 +590,14 @@ async def sleep():
582590
async def coro():
583591
task = self.new_task(loop, sleep())
584592
await asyncio.sleep(0)
585-
task.cancel(*cancel_args)
593+
if cancel_args not in ((), (None,)):
594+
with self.assertWarnsRegex(
595+
DeprecationWarning,
596+
"Passing 'msg' argument"
597+
):
598+
task.cancel(*cancel_args)
599+
else:
600+
task.cancel(*cancel_args)
586601
done, pending = await asyncio.wait([task])
587602
task.result()
588603

@@ -616,7 +631,14 @@ async def sleep():
616631
async def coro():
617632
task = self.new_task(loop, sleep())
618633
await asyncio.sleep(0)
619-
task.cancel(*cancel_args)
634+
if cancel_args not in ((), (None,)):
635+
with self.assertWarnsRegex(
636+
DeprecationWarning,
637+
"Passing 'msg' argument"
638+
):
639+
task.cancel(*cancel_args)
640+
else:
641+
task.cancel(*cancel_args)
620642
done, pending = await asyncio.wait([task])
621643
task.exception()
622644

@@ -639,10 +661,17 @@ async def sleep():
639661
fut.set_result(None)
640662
await asyncio.sleep(10)
641663

664+
def cancel(task, msg):
665+
with self.assertWarnsRegex(
666+
DeprecationWarning,
667+
"Passing 'msg' argument"
668+
):
669+
task.cancel(msg)
670+
642671
async def coro():
643672
inner_task = self.new_task(loop, sleep())
644673
await fut
645-
loop.call_soon(inner_task.cancel, 'msg')
674+
loop.call_soon(cancel, inner_task, 'msg')
646675
try:
647676
await inner_task
648677
except asyncio.CancelledError as ex:
@@ -668,7 +697,11 @@ async def sleep():
668697
async def coro():
669698
task = self.new_task(loop, sleep())
670699
# We deliberately leave out the sleep here.
671-
task.cancel('my message')
700+
with self.assertWarnsRegex(
701+
DeprecationWarning,
702+
"Passing 'msg' argument"
703+
):
704+
task.cancel('my message')
672705
done, pending = await asyncio.wait([task])
673706
task.exception()
674707

@@ -2029,7 +2062,14 @@ async def test():
20292062
async def main():
20302063
qwe = self.new_task(loop, test())
20312064
await asyncio.sleep(0.2)
2032-
qwe.cancel(*cancel_args)
2065+
if cancel_args not in ((), (None,)):
2066+
with self.assertWarnsRegex(
2067+
DeprecationWarning,
2068+
"Passing 'msg' argument"
2069+
):
2070+
qwe.cancel(*cancel_args)
2071+
else:
2072+
qwe.cancel(*cancel_args)
20332073
await qwe
20342074

20352075
try:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Deprecate passing a message into :meth:`asyncio.Future.cancel` and
2+
:meth:`asyncio.Task.cancel`

Modules/_asynciomodule.c

+20
Original file line numberDiff line numberDiff line change
@@ -1096,6 +1096,16 @@ static PyObject *
10961096
_asyncio_Future_cancel_impl(FutureObj *self, PyObject *msg)
10971097
/*[clinic end generated code: output=3edebbc668e5aba3 input=925eb545251f2c5a]*/
10981098
{
1099+
if (msg != Py_None) {
1100+
if (PyErr_WarnEx(PyExc_DeprecationWarning,
1101+
"Passing 'msg' argument to Future.cancel() "
1102+
"is deprecated since Python 3.11, and "
1103+
"scheduled for removal in Python 3.14.",
1104+
2))
1105+
{
1106+
return NULL;
1107+
}
1108+
}
10991109
ENSURE_FUTURE_ALIVE(self)
11001110
return future_cancel(self, msg);
11011111
}
@@ -2176,6 +2186,16 @@ static PyObject *
21762186
_asyncio_Task_cancel_impl(TaskObj *self, PyObject *msg)
21772187
/*[clinic end generated code: output=c66b60d41c74f9f1 input=7bb51bf25974c783]*/
21782188
{
2189+
if (msg != Py_None) {
2190+
if (PyErr_WarnEx(PyExc_DeprecationWarning,
2191+
"Passing 'msg' argument to Task.cancel() "
2192+
"is deprecated since Python 3.11, and "
2193+
"scheduled for removal in Python 3.14.",
2194+
2))
2195+
{
2196+
return NULL;
2197+
}
2198+
}
21792199
self->task_log_tb = 0;
21802200

21812201
if (self->task_state != STATE_PENDING) {

0 commit comments

Comments
 (0)