Skip to content

Commit e4cfc90

Browse files
committed
Don't repeat the cancellation message more than once.
1 parent 0bb6882 commit e4cfc90

File tree

5 files changed

+132
-51
lines changed

5 files changed

+132
-51
lines changed

Lib/asyncio/futures.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,12 @@ def get_loop(self):
126126
raise RuntimeError("Future object is not initialized.")
127127
return loop
128128

129+
def _make_cancelled_error(self):
130+
exc = exceptions.CancelledError(
131+
'' if self._cancel_message is None else self._cancel_message)
132+
exc.__context__ = self._cancelled_exc
133+
return exc
134+
129135
def cancel(self, msg=None):
130136
"""Cancel the future and schedule callbacks.
131137
@@ -177,9 +183,7 @@ def result(self):
177183
the future is done and has an exception set, this exception is raised.
178184
"""
179185
if self._state == _CANCELLED:
180-
exc = exceptions.CancelledError(
181-
'' if self._cancel_message is None else self._cancel_message)
182-
exc.__context__ = self._cancelled_exc
186+
exc = self._make_cancelled_error()
183187
raise exc
184188
if self._state != _FINISHED:
185189
raise exceptions.InvalidStateError('Result is not ready.')
@@ -197,9 +201,7 @@ def exception(self):
197201
InvalidStateError.
198202
"""
199203
if self._state == _CANCELLED:
200-
exc = exceptions.CancelledError(
201-
'' if self._cancel_message is None else self._cancel_message)
202-
exc.__context__ = self._cancelled_exc
204+
exc = self._make_cancelled_error()
203205
raise exc
204206
if self._state != _FINISHED:
205207
raise exceptions.InvalidStateError('Exception is not set.')

Lib/asyncio/tasks.py

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -270,8 +270,7 @@ def __step(self, exc=None):
270270
f'_step(): already done: {self!r}, {exc!r}')
271271
if self._must_cancel:
272272
if not isinstance(exc, exceptions.CancelledError):
273-
exc = exceptions.CancelledError(''
274-
if self._cancel_message is None else self._cancel_message)
273+
exc = self._make_cancelled_error()
275274
self._must_cancel = False
276275
coro = self._coro
277276
self._fut_waiter = None
@@ -295,11 +294,7 @@ def __step(self, exc=None):
295294
except exceptions.CancelledError as exc:
296295
# Save the original exception so we can chain it later.
297296
self._cancelled_exc = exc
298-
if exc.args:
299-
cancel_msg = exc.args[0]
300-
else:
301-
cancel_msg = None
302-
super().cancel(msg=cancel_msg) # I.e., Future.cancel(self).
297+
super().cancel() # I.e., Future.cancel(self).
303298
except (KeyboardInterrupt, SystemExit) as exc:
304299
super().set_exception(exc)
305300
raise
@@ -789,8 +784,7 @@ def _done_callback(fut):
789784
# Check if 'fut' is cancelled first, as
790785
# 'fut.exception()' will *raise* a CancelledError
791786
# instead of returning it.
792-
exc = exceptions.CancelledError(''
793-
if fut._cancel_message is None else fut._cancel_message)
787+
exc = fut._make_cancelled_error()
794788
outer.set_exception(exc)
795789
return
796790
else:
@@ -809,9 +803,7 @@ def _done_callback(fut):
809803
# Check if 'fut' is cancelled first, as
810804
# 'fut.exception()' will *raise* a CancelledError
811805
# instead of returning it.
812-
res = exceptions.CancelledError(
813-
'' if fut._cancel_message is None else
814-
fut._cancel_message)
806+
res = fut._make_cancelled_error()
815807
else:
816808
res = fut.exception()
817809
if res is None:
@@ -822,8 +814,7 @@ def _done_callback(fut):
822814
# If gather is being cancelled we must propagate the
823815
# cancellation regardless of *return_exceptions* argument.
824816
# See issue 32684.
825-
exc = exceptions.CancelledError(''
826-
if fut._cancel_message is None else fut._cancel_message)
817+
exc = fut._make_cancelled_error()
827818
outer.set_exception(exc)
828819
else:
829820
outer.set_result(results)

Lib/test/test_asyncio/test_tasks.py

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,22 @@ def format_coroutine(qualname, state, src, source_traceback, generator=False):
5858
return 'coro=<%s() %s at %s>' % (qualname, state, src)
5959

6060

61+
def get_innermost_context(exc):
62+
"""
63+
Return information about the innermost exception context in the chain.
64+
"""
65+
depth = 0
66+
while True:
67+
context = exc.__context__
68+
if context is None:
69+
break
70+
71+
exc = context
72+
depth += 1
73+
74+
return (type(exc), exc.args, depth)
75+
76+
6177
class Dummy:
6278

6379
def __repr__(self):
@@ -112,9 +128,10 @@ async def coro():
112128
self.assertEqual(t._cancel_message, None)
113129

114130
t.cancel('my message')
131+
self.assertEqual(t._cancel_message, 'my message')
132+
115133
with self.assertRaises(asyncio.CancelledError):
116134
self.loop.run_until_complete(t)
117-
self.assertEqual(t._cancel_message, 'my message')
118135

119136
def test_task_cancel_message_setter(self):
120137
async def coro():
@@ -124,10 +141,8 @@ async def coro():
124141
t._cancel_message = 'my new message'
125142
self.assertEqual(t._cancel_message, 'my new message')
126143

127-
# Also check that the value is used for cancel().
128144
with self.assertRaises(asyncio.CancelledError):
129145
self.loop.run_until_complete(t)
130-
self.assertEqual(t._cancel_message, 'my new message')
131146

132147
def test_task_del_collect(self):
133148
class Evil:
@@ -574,7 +589,11 @@ async def coro():
574589
with self.assertRaises(asyncio.CancelledError) as cm:
575590
loop.run_until_complete(task)
576591
exc = cm.exception
577-
self.assertEqual(exc.args, expected_args)
592+
self.assertEqual(exc.args, ('',))
593+
594+
actual = get_innermost_context(exc)
595+
self.assertEqual(actual,
596+
(asyncio.CancelledError, expected_args, 2))
578597

579598
def test_cancel_with_message_then_future_exception(self):
580599
# Test Future.exception() after calling cancel() with a message.
@@ -604,7 +623,11 @@ async def coro():
604623
with self.assertRaises(asyncio.CancelledError) as cm:
605624
loop.run_until_complete(task)
606625
exc = cm.exception
607-
self.assertEqual(exc.args, expected_args)
626+
self.assertEqual(exc.args, ('',))
627+
628+
actual = get_innermost_context(exc)
629+
self.assertEqual(actual,
630+
(asyncio.CancelledError, expected_args, 2))
608631

609632
def test_cancel_with_message_before_starting_task(self):
610633
loop = asyncio.new_event_loop()
@@ -624,7 +647,11 @@ async def coro():
624647
with self.assertRaises(asyncio.CancelledError) as cm:
625648
loop.run_until_complete(task)
626649
exc = cm.exception
627-
self.assertEqual(exc.args, ('my message',))
650+
self.assertEqual(exc.args, ('',))
651+
652+
actual = get_innermost_context(exc)
653+
self.assertEqual(actual,
654+
(asyncio.CancelledError, ('my message',), 2))
628655

629656
def test_cancel_yield(self):
630657
with self.assertWarns(DeprecationWarning):
@@ -2460,7 +2487,6 @@ def test_cancel_gather_2(self):
24602487
]
24612488
for cancel_args, expected_args in cases:
24622489
with self.subTest(cancel_args=cancel_args):
2463-
24642490
loop = asyncio.new_event_loop()
24652491
self.addCleanup(loop.close)
24662492

@@ -2478,15 +2504,20 @@ async def main():
24782504
qwe = self.new_task(loop, test())
24792505
await asyncio.sleep(0.2)
24802506
qwe.cancel(*cancel_args)
2481-
try:
2482-
await qwe
2483-
except asyncio.CancelledError as exc:
2484-
self.assertEqual(exc.args, expected_args)
2485-
else:
2486-
self.fail('gather did not propagate the cancellation '
2487-
'request')
2488-
2489-
loop.run_until_complete(main())
2507+
await qwe
2508+
2509+
try:
2510+
loop.run_until_complete(main())
2511+
except asyncio.CancelledError as exc:
2512+
self.assertEqual(exc.args, ('',))
2513+
exc_type, exc_args, depth = get_innermost_context(exc)
2514+
self.assertEqual((exc_type, exc_args),
2515+
(asyncio.CancelledError, expected_args))
2516+
# The exact traceback seems to vary in CI.
2517+
self.assertIn(depth, (2, 3))
2518+
else:
2519+
self.fail('gather did not propagate the cancellation '
2520+
'request')
24902521

24912522
def test_exception_traceback(self):
24922523
# See http://bugs.python.org/issue28843

Modules/_asynciomodule.c

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1347,6 +1347,23 @@ FutureObj_get_state(FutureObj *fut, void *Py_UNUSED(ignored))
13471347
return ret;
13481348
}
13491349

1350+
/*[clinic input]
1351+
_asyncio.Future._make_cancelled_error
1352+
1353+
Create the CancelledError to raise if the Future is cancelled.
1354+
[clinic start generated code]*/
1355+
1356+
static PyObject *
1357+
_asyncio_Future__make_cancelled_error_impl(FutureObj *self)
1358+
/*[clinic end generated code: output=a5df276f6c1213de input=374e7bf8dd6edc60]*/
1359+
{
1360+
PyObject *exc = create_cancelled_error(self->fut_cancel_msg);
1361+
_PyErr_StackItem *exc_state = &self->fut_cancelled_exc_state;
1362+
Py_XINCREF(exc_state->exc_value);
1363+
PyException_SetContext(exc, exc_state->exc_value);
1364+
return exc;
1365+
}
1366+
13501367
/*[clinic input]
13511368
_asyncio.Future._repr_info
13521369
[clinic start generated code]*/
@@ -1473,6 +1490,7 @@ static PyMethodDef FutureType_methods[] = {
14731490
_ASYNCIO_FUTURE_CANCELLED_METHODDEF
14741491
_ASYNCIO_FUTURE_DONE_METHODDEF
14751492
_ASYNCIO_FUTURE_GET_LOOP_METHODDEF
1493+
_ASYNCIO_FUTURE__MAKE_CANCELLED_ERROR_METHODDEF
14761494
_ASYNCIO_FUTURE__REPR_INFO_METHODDEF
14771495
{"__class_getitem__", future_cls_getitem, METH_O|METH_CLASS, NULL},
14781496
{NULL, NULL} /* Sentinel */
@@ -2244,6 +2262,21 @@ _asyncio_Task_all_tasks_impl(PyTypeObject *type, PyObject *loop)
22442262
return res;
22452263
}
22462264

2265+
/*[clinic input]
2266+
_asyncio.Task._make_cancelled_error
2267+
2268+
Create the CancelledError to raise if the Task is cancelled.
2269+
[clinic start generated code]*/
2270+
2271+
static PyObject *
2272+
_asyncio_Task__make_cancelled_error_impl(TaskObj *self)
2273+
/*[clinic end generated code: output=55a819e8b4276fab input=fc5485bb07d5c36b]*/
2274+
{
2275+
FutureObj *fut = (FutureObj*)self;
2276+
return _asyncio_Future__make_cancelled_error_impl(fut);
2277+
}
2278+
2279+
22472280
/*[clinic input]
22482281
_asyncio.Task._repr_info
22492282
[clinic start generated code]*/
@@ -2551,6 +2584,7 @@ static PyMethodDef TaskType_methods[] = {
25512584
_ASYNCIO_TASK_CANCEL_METHODDEF
25522585
_ASYNCIO_TASK_GET_STACK_METHODDEF
25532586
_ASYNCIO_TASK_PRINT_STACK_METHODDEF
2587+
_ASYNCIO_TASK__MAKE_CANCELLED_ERROR_METHODDEF
25542588
_ASYNCIO_TASK__REPR_INFO_METHODDEF
25552589
_ASYNCIO_TASK_GET_NAME_METHODDEF
25562590
_ASYNCIO_TASK_SET_NAME_METHODDEF
@@ -2766,26 +2800,13 @@ task_step_impl(TaskObj *task, PyObject *exc)
27662800
/* CancelledError */
27672801
PyErr_Fetch(&et, &ev, &tb);
27682802

2769-
PyObject *cancel_msg;
2770-
if (ev != NULL && PyExceptionInstance_Check(ev)) {
2771-
PyObject *exc_args = ((PyBaseExceptionObject*)ev)->args;
2772-
Py_ssize_t size = PyTuple_GET_SIZE(exc_args);
2773-
if (size > 0) {
2774-
cancel_msg = PyTuple_GET_ITEM(exc_args, 0);
2775-
} else {
2776-
cancel_msg = NULL;
2777-
}
2778-
} else {
2779-
cancel_msg = ev;
2780-
}
2781-
27822803
FutureObj *fut = (FutureObj*)task;
27832804
_PyErr_StackItem *exc_state = &fut->fut_cancelled_exc_state;
27842805
exc_state->exc_type = et;
27852806
exc_state->exc_value = ev;
27862807
exc_state->exc_traceback = tb;
27872808

2788-
return future_cancel(fut, cancel_msg);
2809+
return future_cancel(fut, NULL);
27892810
}
27902811

27912812
/* Some other exception; pop it and call Task.set_exception() */

Modules/clinic/_asynciomodule.c.h

Lines changed: 37 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)