bpo-31033: Add a msg argument to Future.cancel() and Task.cancel() (GH-19979)

This commit is contained in:
Chris Jerdonek 2020-05-15 16:55:50 -07:00 committed by GitHub
parent fe1176e882
commit 1ce5841eca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 355 additions and 70 deletions

View File

@ -170,7 +170,7 @@ Future Object
Returns the number of callbacks removed, which is typically 1, Returns the number of callbacks removed, which is typically 1,
unless a callback was added more than once. unless a callback was added more than once.
.. method:: cancel() .. method:: cancel(msg=None)
Cancel the Future and schedule callbacks. Cancel the Future and schedule callbacks.
@ -178,6 +178,9 @@ Future Object
Otherwise, change the Future's state to *cancelled*, Otherwise, change the Future's state to *cancelled*,
schedule the callbacks, and return ``True``. schedule the callbacks, and return ``True``.
.. versionchanged:: 3.9
Added the ``msg`` parameter.
.. method:: exception() .. method:: exception()
Return the exception that was set on this Future. Return the exception that was set on this Future.
@ -255,3 +258,6 @@ the Future has a result::
- asyncio Future is not compatible with the - asyncio Future is not compatible with the
:func:`concurrent.futures.wait` and :func:`concurrent.futures.wait` and
:func:`concurrent.futures.as_completed` functions. :func:`concurrent.futures.as_completed` functions.
- :meth:`asyncio.Future.cancel` accepts an optional ``msg`` argument,
but :func:`concurrent.futures.cancel` does not.

View File

@ -724,7 +724,7 @@ Task Object
.. deprecated-removed:: 3.8 3.10 .. deprecated-removed:: 3.8 3.10
The *loop* parameter. The *loop* parameter.
.. method:: cancel() .. method:: cancel(msg=None)
Request the Task to be cancelled. Request the Task to be cancelled.
@ -739,6 +739,9 @@ Task Object
suppressing cancellation completely is not common and is actively suppressing cancellation completely is not common and is actively
discouraged. discouraged.
.. versionchanged:: 3.9
Added the ``msg`` parameter.
.. _asyncio_example_task_cancel: .. _asyncio_example_task_cancel:
The following example illustrates how coroutines can intercept The following example illustrates how coroutines can intercept

View File

@ -51,6 +51,7 @@ class Future:
_exception = None _exception = None
_loop = None _loop = None
_source_traceback = None _source_traceback = None
_cancel_message = None
# This field is used for a dual purpose: # This field is used for a dual purpose:
# - Its presence is a marker to declare that a class implements # - Its presence is a marker to declare that a class implements
@ -123,7 +124,7 @@ class Future:
raise RuntimeError("Future object is not initialized.") raise RuntimeError("Future object is not initialized.")
return loop return loop
def cancel(self): def cancel(self, msg=None):
"""Cancel the future and schedule callbacks. """Cancel the future and schedule callbacks.
If the future is already done or cancelled, return False. Otherwise, If the future is already done or cancelled, return False. Otherwise,
@ -134,6 +135,7 @@ class Future:
if self._state != _PENDING: if self._state != _PENDING:
return False return False
self._state = _CANCELLED self._state = _CANCELLED
self._cancel_message = msg
self.__schedule_callbacks() self.__schedule_callbacks()
return True return True
@ -173,7 +175,9 @@ class Future:
the future is done and has an exception set, this exception is raised. the future is done and has an exception set, this exception is raised.
""" """
if self._state == _CANCELLED: if self._state == _CANCELLED:
raise exceptions.CancelledError raise exceptions.CancelledError(
'' if self._cancel_message is None else self._cancel_message)
if self._state != _FINISHED: if self._state != _FINISHED:
raise exceptions.InvalidStateError('Result is not ready.') raise exceptions.InvalidStateError('Result is not ready.')
self.__log_traceback = False self.__log_traceback = False
@ -190,7 +194,8 @@ class Future:
InvalidStateError. InvalidStateError.
""" """
if self._state == _CANCELLED: if self._state == _CANCELLED:
raise exceptions.CancelledError raise exceptions.CancelledError(
'' if self._cancel_message is None else self._cancel_message)
if self._state != _FINISHED: if self._state != _FINISHED:
raise exceptions.InvalidStateError('Exception is not set.') raise exceptions.InvalidStateError('Exception is not set.')
self.__log_traceback = False self.__log_traceback = False

View File

@ -230,7 +230,7 @@ class Task(futures._PyFuture): # Inherit Python Task implementation
""" """
return base_tasks._task_print_stack(self, limit, file) return base_tasks._task_print_stack(self, limit, file)
def cancel(self): def cancel(self, msg=None):
"""Request that this task cancel itself. """Request that this task cancel itself.
This arranges for a CancelledError to be thrown into the This arranges for a CancelledError to be thrown into the
@ -254,13 +254,14 @@ class Task(futures._PyFuture): # Inherit Python Task implementation
if self.done(): if self.done():
return False return False
if self._fut_waiter is not None: if self._fut_waiter is not None:
if self._fut_waiter.cancel(): if self._fut_waiter.cancel(msg=msg):
# Leave self._fut_waiter; it may be a Task that # Leave self._fut_waiter; it may be a Task that
# catches and ignores the cancellation so we may have # catches and ignores the cancellation so we may have
# to cancel it again later. # to cancel it again later.
return True return True
# It must be the case that self.__step is already scheduled. # It must be the case that self.__step is already scheduled.
self._must_cancel = True self._must_cancel = True
self._cancel_message = msg
return True return True
def __step(self, exc=None): def __step(self, exc=None):
@ -269,7 +270,8 @@ class Task(futures._PyFuture): # Inherit Python Task implementation
f'_step(): already done: {self!r}, {exc!r}') f'_step(): already done: {self!r}, {exc!r}')
if self._must_cancel: if self._must_cancel:
if not isinstance(exc, exceptions.CancelledError): if not isinstance(exc, exceptions.CancelledError):
exc = exceptions.CancelledError() exc = exceptions.CancelledError(''
if self._cancel_message is None else self._cancel_message)
self._must_cancel = False self._must_cancel = False
coro = self._coro coro = self._coro
self._fut_waiter = None self._fut_waiter = None
@ -287,11 +289,15 @@ class Task(futures._PyFuture): # Inherit Python Task implementation
if self._must_cancel: if self._must_cancel:
# Task is cancelled right before coro stops. # Task is cancelled right before coro stops.
self._must_cancel = False self._must_cancel = False
super().cancel() super().cancel(msg=self._cancel_message)
else: else:
super().set_result(exc.value) super().set_result(exc.value)
except exceptions.CancelledError: except exceptions.CancelledError as exc:
super().cancel() # I.e., Future.cancel(self). if exc.args:
cancel_msg = exc.args[0]
else:
cancel_msg = None
super().cancel(msg=cancel_msg) # I.e., Future.cancel(self).
except (KeyboardInterrupt, SystemExit) as exc: except (KeyboardInterrupt, SystemExit) as exc:
super().set_exception(exc) super().set_exception(exc)
raise raise
@ -319,7 +325,8 @@ class Task(futures._PyFuture): # Inherit Python Task implementation
self.__wakeup, context=self._context) self.__wakeup, context=self._context)
self._fut_waiter = result self._fut_waiter = result
if self._must_cancel: if self._must_cancel:
if self._fut_waiter.cancel(): if self._fut_waiter.cancel(
msg=self._cancel_message):
self._must_cancel = False self._must_cancel = False
else: else:
new_exc = RuntimeError( new_exc = RuntimeError(
@ -716,12 +723,12 @@ class _GatheringFuture(futures.Future):
self._children = children self._children = children
self._cancel_requested = False self._cancel_requested = False
def cancel(self): def cancel(self, msg=None):
if self.done(): if self.done():
return False return False
ret = False ret = False
for child in self._children: for child in self._children:
if child.cancel(): if child.cancel(msg=msg):
ret = True ret = True
if ret: if ret:
# If any child tasks were actually cancelled, we should # If any child tasks were actually cancelled, we should
@ -780,7 +787,8 @@ def gather(*coros_or_futures, loop=None, return_exceptions=False):
# Check if 'fut' is cancelled first, as # Check if 'fut' is cancelled first, as
# 'fut.exception()' will *raise* a CancelledError # 'fut.exception()' will *raise* a CancelledError
# instead of returning it. # instead of returning it.
exc = exceptions.CancelledError() exc = exceptions.CancelledError(''
if fut._cancel_message is None else fut._cancel_message)
outer.set_exception(exc) outer.set_exception(exc)
return return
else: else:
@ -799,7 +807,9 @@ def gather(*coros_or_futures, loop=None, return_exceptions=False):
# Check if 'fut' is cancelled first, as # Check if 'fut' is cancelled first, as
# 'fut.exception()' will *raise* a CancelledError # 'fut.exception()' will *raise* a CancelledError
# instead of returning it. # instead of returning it.
res = exceptions.CancelledError() res = exceptions.CancelledError(
'' if fut._cancel_message is None else
fut._cancel_message)
else: else:
res = fut.exception() res = fut.exception()
if res is None: if res is None:
@ -810,7 +820,9 @@ def gather(*coros_or_futures, loop=None, return_exceptions=False):
# If gather is being cancelled we must propagate the # If gather is being cancelled we must propagate the
# cancellation regardless of *return_exceptions* argument. # cancellation regardless of *return_exceptions* argument.
# See issue 32684. # See issue 32684.
outer.set_exception(exceptions.CancelledError()) exc = exceptions.CancelledError(''
if fut._cancel_message is None else fut._cancel_message)
outer.set_exception(exc)
else: else:
outer.set_result(results) outer.set_result(results)

View File

@ -75,9 +75,9 @@ class _OverlappedFuture(futures.Future):
self._loop.call_exception_handler(context) self._loop.call_exception_handler(context)
self._ov = None self._ov = None
def cancel(self): def cancel(self, msg=None):
self._cancel_overlapped() self._cancel_overlapped()
return super().cancel() return super().cancel(msg=msg)
def set_exception(self, exception): def set_exception(self, exception):
super().set_exception(exception) super().set_exception(exception)
@ -149,9 +149,9 @@ class _BaseWaitHandleFuture(futures.Future):
self._unregister_wait_cb(None) self._unregister_wait_cb(None)
def cancel(self): def cancel(self, msg=None):
self._unregister_wait() self._unregister_wait()
return super().cancel() return super().cancel(msg=msg)
def set_exception(self, exception): def set_exception(self, exception):
self._unregister_wait() self._unregister_wait()

View File

@ -201,6 +201,27 @@ class BaseFutureTests:
self.assertFalse(fut.cancelled()) self.assertFalse(fut.cancelled())
self.assertFalse(fut.done()) self.assertFalse(fut.done())
def test_future_cancel_message_getter(self):
f = self._new_future(loop=self.loop)
self.assertTrue(hasattr(f, '_cancel_message'))
self.assertEqual(f._cancel_message, None)
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')
f._cancel_message = 'my new message'
self.assertEqual(f._cancel_message, 'my new message')
# Also check that the value is used for cancel().
with self.assertRaises(asyncio.CancelledError):
self.loop.run_until_complete(f)
self.assertEqual(f._cancel_message, 'my new message')
def test_cancel(self): def test_cancel(self):
f = self._new_future(loop=self.loop) f = self._new_future(loop=self.loop)
self.assertTrue(f.cancel()) self.assertTrue(f.cancel())

View File

@ -103,6 +103,31 @@ class BaseTaskTests:
self.loop.set_task_factory(self.new_task) self.loop.set_task_factory(self.new_task)
self.loop.create_future = lambda: self.new_future(self.loop) self.loop.create_future = lambda: self.new_future(self.loop)
def test_task_cancel_message_getter(self):
async def coro():
pass
t = self.new_task(self.loop, coro())
self.assertTrue(hasattr(t, '_cancel_message'))
self.assertEqual(t._cancel_message, None)
t.cancel('my message')
with self.assertRaises(asyncio.CancelledError):
self.loop.run_until_complete(t)
self.assertEqual(t._cancel_message, 'my message')
def test_task_cancel_message_setter(self):
async def coro():
pass
t = self.new_task(self.loop, coro())
t.cancel('my message')
t._cancel_message = 'my new message'
self.assertEqual(t._cancel_message, 'my new message')
# Also check that the value is used for cancel().
with self.assertRaises(asyncio.CancelledError):
self.loop.run_until_complete(t)
self.assertEqual(t._cancel_message, 'my new message')
def test_task_del_collect(self): def test_task_del_collect(self):
class Evil: class Evil:
def __del__(self): def __del__(self):
@ -520,6 +545,86 @@ class BaseTaskTests:
self.assertTrue(t.cancelled()) self.assertTrue(t.cancelled())
self.assertFalse(t.cancel()) self.assertFalse(t.cancel())
def test_cancel_with_message_then_future_result(self):
# Test Future.result() after calling cancel() with a message.
cases = [
((), ('',)),
((None,), ('',)),
(('my message',), ('my message',)),
# Non-string values should roundtrip.
((5,), (5,)),
]
for cancel_args, expected_args in cases:
with self.subTest(cancel_args=cancel_args):
loop = asyncio.new_event_loop()
self.set_event_loop(loop)
async def sleep():
await asyncio.sleep(10)
async def coro():
task = self.new_task(loop, sleep())
await asyncio.sleep(0)
task.cancel(*cancel_args)
done, pending = await asyncio.wait([task])
task.result()
task = self.new_task(loop, coro())
with self.assertRaises(asyncio.CancelledError) as cm:
loop.run_until_complete(task)
exc = cm.exception
self.assertEqual(exc.args, expected_args)
def test_cancel_with_message_then_future_exception(self):
# Test Future.exception() after calling cancel() with a message.
cases = [
((), ('',)),
((None,), ('',)),
(('my message',), ('my message',)),
# Non-string values should roundtrip.
((5,), (5,)),
]
for cancel_args, expected_args in cases:
with self.subTest(cancel_args=cancel_args):
loop = asyncio.new_event_loop()
self.set_event_loop(loop)
async def sleep():
await asyncio.sleep(10)
async def coro():
task = self.new_task(loop, sleep())
await asyncio.sleep(0)
task.cancel(*cancel_args)
done, pending = await asyncio.wait([task])
task.exception()
task = self.new_task(loop, coro())
with self.assertRaises(asyncio.CancelledError) as cm:
loop.run_until_complete(task)
exc = cm.exception
self.assertEqual(exc.args, expected_args)
def test_cancel_with_message_before_starting_task(self):
loop = asyncio.new_event_loop()
self.set_event_loop(loop)
async def sleep():
await asyncio.sleep(10)
async def coro():
task = self.new_task(loop, sleep())
# We deliberately leave out the sleep here.
task.cancel('my message')
done, pending = await asyncio.wait([task])
task.exception()
task = self.new_task(loop, coro())
with self.assertRaises(asyncio.CancelledError) as cm:
loop.run_until_complete(task)
exc = cm.exception
self.assertEqual(exc.args, ('my message',))
def test_cancel_yield(self): def test_cancel_yield(self):
with self.assertWarns(DeprecationWarning): with self.assertWarns(DeprecationWarning):
@asyncio.coroutine @asyncio.coroutine
@ -2285,31 +2390,42 @@ class BaseTaskTests:
self.assertEqual(gather_task.result(), [42]) self.assertEqual(gather_task.result(), [42])
def test_cancel_gather_2(self): def test_cancel_gather_2(self):
loop = asyncio.new_event_loop() cases = [
self.addCleanup(loop.close) ((), ('',)),
((None,), ('',)),
(('my message',), ('my message',)),
# Non-string values should roundtrip.
((5,), (5,)),
]
for cancel_args, expected_args in cases:
with self.subTest(cancel_args=cancel_args):
async def test(): loop = asyncio.new_event_loop()
time = 0 self.addCleanup(loop.close)
while True:
time += 0.05
await asyncio.gather(asyncio.sleep(0.05),
return_exceptions=True,
loop=loop)
if time > 1:
return
async def main(): async def test():
qwe = self.new_task(loop, test()) time = 0
await asyncio.sleep(0.2) while True:
qwe.cancel() time += 0.05
try: await asyncio.gather(asyncio.sleep(0.05),
await qwe return_exceptions=True,
except asyncio.CancelledError: loop=loop)
pass if time > 1:
else: return
self.fail('gather did not propagate the cancellation request')
loop.run_until_complete(main()) async def main():
qwe = self.new_task(loop, test())
await asyncio.sleep(0.2)
qwe.cancel(*cancel_args)
try:
await qwe
except asyncio.CancelledError as exc:
self.assertEqual(exc.args, expected_args)
else:
self.fail('gather did not propagate the cancellation '
'request')
loop.run_until_complete(main())
def test_exception_traceback(self): def test_exception_traceback(self):
# See http://bugs.python.org/issue28843 # See http://bugs.python.org/issue28843

View File

@ -0,0 +1 @@
Add a ``msg`` argument to :meth:`Future.cancel` and :meth:`Task.cancel`.

View File

@ -67,6 +67,7 @@ typedef enum {
PyObject *prefix##_exception; \ PyObject *prefix##_exception; \
PyObject *prefix##_result; \ PyObject *prefix##_result; \
PyObject *prefix##_source_tb; \ PyObject *prefix##_source_tb; \
PyObject *prefix##_cancel_msg; \
fut_state prefix##_state; \ fut_state prefix##_state; \
int prefix##_log_tb; \ int prefix##_log_tb; \
int prefix##_blocking; \ int prefix##_blocking; \
@ -480,6 +481,7 @@ future_init(FutureObj *fut, PyObject *loop)
Py_CLEAR(fut->fut_result); Py_CLEAR(fut->fut_result);
Py_CLEAR(fut->fut_exception); Py_CLEAR(fut->fut_exception);
Py_CLEAR(fut->fut_source_tb); Py_CLEAR(fut->fut_source_tb);
Py_CLEAR(fut->fut_cancel_msg);
fut->fut_state = STATE_PENDING; fut->fut_state = STATE_PENDING;
fut->fut_log_tb = 0; fut->fut_log_tb = 0;
@ -594,11 +596,33 @@ future_set_exception(FutureObj *fut, PyObject *exc)
Py_RETURN_NONE; Py_RETURN_NONE;
} }
static PyObject *
create_cancelled_error(PyObject *msg)
{
PyObject *exc;
if (msg == NULL || msg == Py_None) {
msg = PyUnicode_FromString("");
exc = PyObject_CallOneArg(asyncio_CancelledError, msg);
Py_DECREF(msg);
} else {
exc = PyObject_CallOneArg(asyncio_CancelledError, msg);
}
return exc;
}
static void
set_cancelled_error(PyObject *msg)
{
PyObject *exc = create_cancelled_error(msg);
PyErr_SetObject(asyncio_CancelledError, exc);
Py_DECREF(exc);
}
static int static int
future_get_result(FutureObj *fut, PyObject **result) future_get_result(FutureObj *fut, PyObject **result)
{ {
if (fut->fut_state == STATE_CANCELLED) { if (fut->fut_state == STATE_CANCELLED) {
PyErr_SetNone(asyncio_CancelledError); set_cancelled_error(fut->fut_cancel_msg);
return -1; return -1;
} }
@ -695,7 +719,7 @@ future_add_done_callback(FutureObj *fut, PyObject *arg, PyObject *ctx)
} }
static PyObject * static PyObject *
future_cancel(FutureObj *fut) future_cancel(FutureObj *fut, PyObject *msg)
{ {
fut->fut_log_tb = 0; fut->fut_log_tb = 0;
@ -704,6 +728,9 @@ future_cancel(FutureObj *fut)
} }
fut->fut_state = STATE_CANCELLED; fut->fut_state = STATE_CANCELLED;
Py_XINCREF(msg);
Py_XSETREF(fut->fut_cancel_msg, msg);
if (future_schedule_callbacks(fut) == -1) { if (future_schedule_callbacks(fut) == -1) {
return NULL; return NULL;
} }
@ -749,6 +776,7 @@ FutureObj_clear(FutureObj *fut)
Py_CLEAR(fut->fut_result); Py_CLEAR(fut->fut_result);
Py_CLEAR(fut->fut_exception); Py_CLEAR(fut->fut_exception);
Py_CLEAR(fut->fut_source_tb); Py_CLEAR(fut->fut_source_tb);
Py_CLEAR(fut->fut_cancel_msg);
Py_CLEAR(fut->dict); Py_CLEAR(fut->dict);
return 0; return 0;
} }
@ -763,6 +791,7 @@ FutureObj_traverse(FutureObj *fut, visitproc visit, void *arg)
Py_VISIT(fut->fut_result); Py_VISIT(fut->fut_result);
Py_VISIT(fut->fut_exception); Py_VISIT(fut->fut_exception);
Py_VISIT(fut->fut_source_tb); Py_VISIT(fut->fut_source_tb);
Py_VISIT(fut->fut_cancel_msg);
Py_VISIT(fut->dict); Py_VISIT(fut->dict);
return 0; return 0;
} }
@ -828,7 +857,7 @@ _asyncio_Future_exception_impl(FutureObj *self)
} }
if (self->fut_state == STATE_CANCELLED) { if (self->fut_state == STATE_CANCELLED) {
PyErr_SetNone(asyncio_CancelledError); set_cancelled_error(self->fut_cancel_msg);
return NULL; return NULL;
} }
@ -1029,6 +1058,8 @@ fail:
/*[clinic input] /*[clinic input]
_asyncio.Future.cancel _asyncio.Future.cancel
msg: object = None
Cancel the future and schedule callbacks. Cancel the future and schedule callbacks.
If the future is already done or cancelled, return False. Otherwise, If the future is already done or cancelled, return False. Otherwise,
@ -1037,11 +1068,11 @@ return True.
[clinic start generated code]*/ [clinic start generated code]*/
static PyObject * static PyObject *
_asyncio_Future_cancel_impl(FutureObj *self) _asyncio_Future_cancel_impl(FutureObj *self, PyObject *msg)
/*[clinic end generated code: output=e45b932ba8bd68a1 input=515709a127995109]*/ /*[clinic end generated code: output=3edebbc668e5aba3 input=925eb545251f2c5a]*/
{ {
ENSURE_FUTURE_ALIVE(self) ENSURE_FUTURE_ALIVE(self)
return future_cancel(self); return future_cancel(self, msg);
} }
/*[clinic input] /*[clinic input]
@ -1254,6 +1285,29 @@ FutureObj_get_source_traceback(FutureObj *fut, void *Py_UNUSED(ignored))
return fut->fut_source_tb; return fut->fut_source_tb;
} }
static PyObject *
FutureObj_get_cancel_message(FutureObj *fut, void *Py_UNUSED(ignored))
{
if (fut->fut_cancel_msg == NULL) {
Py_RETURN_NONE;
}
Py_INCREF(fut->fut_cancel_msg);
return fut->fut_cancel_msg;
}
static int
FutureObj_set_cancel_message(FutureObj *fut, PyObject *msg,
void *Py_UNUSED(ignored))
{
if (msg == NULL) {
PyErr_SetString(PyExc_AttributeError, "cannot delete attribute");
return -1;
}
Py_INCREF(msg);
Py_XSETREF(fut->fut_cancel_msg, msg);
return 0;
}
static PyObject * static PyObject *
FutureObj_get_state(FutureObj *fut, void *Py_UNUSED(ignored)) FutureObj_get_state(FutureObj *fut, void *Py_UNUSED(ignored))
{ {
@ -1422,7 +1476,10 @@ static PyMethodDef FutureType_methods[] = {
{"_exception", (getter)FutureObj_get_exception, NULL, NULL}, \ {"_exception", (getter)FutureObj_get_exception, NULL, NULL}, \
{"_log_traceback", (getter)FutureObj_get_log_traceback, \ {"_log_traceback", (getter)FutureObj_get_log_traceback, \
(setter)FutureObj_set_log_traceback, NULL}, \ (setter)FutureObj_set_log_traceback, NULL}, \
{"_source_traceback", (getter)FutureObj_get_source_traceback, NULL, NULL}, {"_source_traceback", (getter)FutureObj_get_source_traceback, \
NULL, NULL}, \
{"_cancel_message", (getter)FutureObj_get_cancel_message, \
(setter)FutureObj_set_cancel_message, NULL},
static PyGetSetDef FutureType_getsetlist[] = { static PyGetSetDef FutureType_getsetlist[] = {
FUTURE_COMMON_GETSETLIST FUTURE_COMMON_GETSETLIST
@ -2189,6 +2246,8 @@ _asyncio_Task__repr_info_impl(TaskObj *self)
/*[clinic input] /*[clinic input]
_asyncio.Task.cancel _asyncio.Task.cancel
msg: object = None
Request that this task cancel itself. Request that this task cancel itself.
This arranges for a CancelledError to be thrown into the This arranges for a CancelledError to be thrown into the
@ -2210,8 +2269,8 @@ was not called).
[clinic start generated code]*/ [clinic start generated code]*/
static PyObject * static PyObject *
_asyncio_Task_cancel_impl(TaskObj *self) _asyncio_Task_cancel_impl(TaskObj *self, PyObject *msg)
/*[clinic end generated code: output=6bfc0479da9d5757 input=13f9bf496695cb52]*/ /*[clinic end generated code: output=c66b60d41c74f9f1 input=f4ff8e8ffc5f1c00]*/
{ {
self->task_log_tb = 0; self->task_log_tb = 0;
@ -2223,7 +2282,8 @@ _asyncio_Task_cancel_impl(TaskObj *self)
PyObject *res; PyObject *res;
int is_true; int is_true;
res = _PyObject_CallMethodIdNoArgs(self->task_fut_waiter, &PyId_cancel); res = _PyObject_CallMethodIdOneArg(self->task_fut_waiter,
&PyId_cancel, msg);
if (res == NULL) { if (res == NULL) {
return NULL; return NULL;
} }
@ -2240,6 +2300,8 @@ _asyncio_Task_cancel_impl(TaskObj *self)
} }
self->task_must_cancel = 1; self->task_must_cancel = 1;
Py_XINCREF(msg);
Py_XSETREF(self->task_cancel_msg, msg);
Py_RETURN_TRUE; Py_RETURN_TRUE;
} }
@ -2623,7 +2685,8 @@ task_step_impl(TaskObj *task, PyObject *exc)
if (!exc) { if (!exc) {
/* exc was not a CancelledError */ /* exc was not a CancelledError */
exc = PyObject_CallNoArgs(asyncio_CancelledError); exc = create_cancelled_error(task->task_cancel_msg);
if (!exc) { if (!exc) {
goto fail; goto fail;
} }
@ -2672,7 +2735,7 @@ task_step_impl(TaskObj *task, PyObject *exc)
if (task->task_must_cancel) { if (task->task_must_cancel) {
// Task is cancelled right before coro stops. // Task is cancelled right before coro stops.
task->task_must_cancel = 0; task->task_must_cancel = 0;
res = future_cancel((FutureObj*)task); res = future_cancel((FutureObj*)task, task->task_cancel_msg);
} }
else { else {
res = future_set_result((FutureObj*)task, o); res = future_set_result((FutureObj*)task, o);
@ -2689,8 +2752,26 @@ task_step_impl(TaskObj *task, PyObject *exc)
if (PyErr_ExceptionMatches(asyncio_CancelledError)) { if (PyErr_ExceptionMatches(asyncio_CancelledError)) {
/* CancelledError */ /* CancelledError */
PyErr_Clear(); PyErr_Fetch(&et, &ev, &tb);
return future_cancel((FutureObj*)task);
PyObject *cancel_msg;
if (ev != NULL && PyExceptionInstance_Check(ev)) {
PyObject *exc_args = ((PyBaseExceptionObject*)ev)->args;
Py_ssize_t size = PyTuple_GET_SIZE(exc_args);
if (size > 0) {
cancel_msg = PyTuple_GET_ITEM(exc_args, 0);
} else {
cancel_msg = NULL;
}
} else {
cancel_msg = ev;
}
Py_DECREF(et);
Py_XDECREF(tb);
Py_XDECREF(ev);
return future_cancel((FutureObj*)task, cancel_msg);
} }
/* Some other exception; pop it and call Task.set_exception() */ /* Some other exception; pop it and call Task.set_exception() */
@ -2770,7 +2851,8 @@ task_step_impl(TaskObj *task, PyObject *exc)
if (task->task_must_cancel) { if (task->task_must_cancel) {
PyObject *r; PyObject *r;
int is_true; int is_true;
r = _PyObject_CallMethodIdNoArgs(result, &PyId_cancel); r = _PyObject_CallMethodIdOneArg(result, &PyId_cancel,
task->task_cancel_msg);
if (r == NULL) { if (r == NULL) {
return NULL; return NULL;
} }
@ -2861,7 +2943,8 @@ task_step_impl(TaskObj *task, PyObject *exc)
if (task->task_must_cancel) { if (task->task_must_cancel) {
PyObject *r; PyObject *r;
int is_true; int is_true;
r = _PyObject_CallMethodIdNoArgs(result, &PyId_cancel); r = _PyObject_CallMethodIdOneArg(result, &PyId_cancel,
task->task_cancel_msg);
if (r == NULL) { if (r == NULL) {
return NULL; return NULL;
} }

View File

@ -174,7 +174,7 @@ PyDoc_STRVAR(_asyncio_Future_remove_done_callback__doc__,
{"remove_done_callback", (PyCFunction)_asyncio_Future_remove_done_callback, METH_O, _asyncio_Future_remove_done_callback__doc__}, {"remove_done_callback", (PyCFunction)_asyncio_Future_remove_done_callback, METH_O, _asyncio_Future_remove_done_callback__doc__},
PyDoc_STRVAR(_asyncio_Future_cancel__doc__, PyDoc_STRVAR(_asyncio_Future_cancel__doc__,
"cancel($self, /)\n" "cancel($self, /, msg=None)\n"
"--\n" "--\n"
"\n" "\n"
"Cancel the future and schedule callbacks.\n" "Cancel the future and schedule callbacks.\n"
@ -184,15 +184,34 @@ PyDoc_STRVAR(_asyncio_Future_cancel__doc__,
"return True."); "return True.");
#define _ASYNCIO_FUTURE_CANCEL_METHODDEF \ #define _ASYNCIO_FUTURE_CANCEL_METHODDEF \
{"cancel", (PyCFunction)_asyncio_Future_cancel, METH_NOARGS, _asyncio_Future_cancel__doc__}, {"cancel", (PyCFunction)(void(*)(void))_asyncio_Future_cancel, METH_FASTCALL|METH_KEYWORDS, _asyncio_Future_cancel__doc__},
static PyObject * static PyObject *
_asyncio_Future_cancel_impl(FutureObj *self); _asyncio_Future_cancel_impl(FutureObj *self, PyObject *msg);
static PyObject * static PyObject *
_asyncio_Future_cancel(FutureObj *self, PyObject *Py_UNUSED(ignored)) _asyncio_Future_cancel(FutureObj *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{ {
return _asyncio_Future_cancel_impl(self); PyObject *return_value = NULL;
static const char * const _keywords[] = {"msg", NULL};
static _PyArg_Parser _parser = {NULL, _keywords, "cancel", 0};
PyObject *argsbuf[1];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
PyObject *msg = Py_None;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 1, 0, argsbuf);
if (!args) {
goto exit;
}
if (!noptargs) {
goto skip_optional_pos;
}
msg = args[0];
skip_optional_pos:
return_value = _asyncio_Future_cancel_impl(self, msg);
exit:
return return_value;
} }
PyDoc_STRVAR(_asyncio_Future_cancelled__doc__, PyDoc_STRVAR(_asyncio_Future_cancelled__doc__,
@ -413,7 +432,7 @@ _asyncio_Task__repr_info(TaskObj *self, PyObject *Py_UNUSED(ignored))
} }
PyDoc_STRVAR(_asyncio_Task_cancel__doc__, PyDoc_STRVAR(_asyncio_Task_cancel__doc__,
"cancel($self, /)\n" "cancel($self, /, msg=None)\n"
"--\n" "--\n"
"\n" "\n"
"Request that this task cancel itself.\n" "Request that this task cancel itself.\n"
@ -436,15 +455,34 @@ PyDoc_STRVAR(_asyncio_Task_cancel__doc__,
"was not called)."); "was not called).");
#define _ASYNCIO_TASK_CANCEL_METHODDEF \ #define _ASYNCIO_TASK_CANCEL_METHODDEF \
{"cancel", (PyCFunction)_asyncio_Task_cancel, METH_NOARGS, _asyncio_Task_cancel__doc__}, {"cancel", (PyCFunction)(void(*)(void))_asyncio_Task_cancel, METH_FASTCALL|METH_KEYWORDS, _asyncio_Task_cancel__doc__},
static PyObject * static PyObject *
_asyncio_Task_cancel_impl(TaskObj *self); _asyncio_Task_cancel_impl(TaskObj *self, PyObject *msg);
static PyObject * static PyObject *
_asyncio_Task_cancel(TaskObj *self, PyObject *Py_UNUSED(ignored)) _asyncio_Task_cancel(TaskObj *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{ {
return _asyncio_Task_cancel_impl(self); PyObject *return_value = NULL;
static const char * const _keywords[] = {"msg", NULL};
static _PyArg_Parser _parser = {NULL, _keywords, "cancel", 0};
PyObject *argsbuf[1];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
PyObject *msg = Py_None;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 1, 0, argsbuf);
if (!args) {
goto exit;
}
if (!noptargs) {
goto skip_optional_pos;
}
msg = args[0];
skip_optional_pos:
return_value = _asyncio_Task_cancel_impl(self, msg);
exit:
return return_value;
} }
PyDoc_STRVAR(_asyncio_Task_get_stack__doc__, PyDoc_STRVAR(_asyncio_Task_get_stack__doc__,
@ -832,4 +870,4 @@ _asyncio__leave_task(PyObject *module, PyObject *const *args, Py_ssize_t nargs,
exit: exit:
return return_value; return return_value;
} }
/*[clinic end generated code: output=585ba1f8de5b4103 input=a9049054013a1b77]*/ /*[clinic end generated code: output=6ed4cfda8fc516ad input=a9049054013a1b77]*/