mirror of https://github.com/python/cpython
bpo-31033: Improve the traceback for cancelled asyncio tasks (GH-19951)
When an asyncio.Task is cancelled, the exception traceback now starts with where the task was first interrupted. Previously, the traceback only had "depth one."
This commit is contained in:
parent
d17f3d8315
commit
da742ba826
|
@ -14,6 +14,20 @@ static inline PyObject* _PyErr_Occurred(PyThreadState *tstate)
|
||||||
return tstate->curexc_type;
|
return tstate->curexc_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void _PyErr_ClearExcState(_PyErr_StackItem *exc_state)
|
||||||
|
{
|
||||||
|
PyObject *t, *v, *tb;
|
||||||
|
t = exc_state->exc_type;
|
||||||
|
v = exc_state->exc_value;
|
||||||
|
tb = exc_state->exc_traceback;
|
||||||
|
exc_state->exc_type = NULL;
|
||||||
|
exc_state->exc_value = NULL;
|
||||||
|
exc_state->exc_traceback = NULL;
|
||||||
|
Py_XDECREF(t);
|
||||||
|
Py_XDECREF(v);
|
||||||
|
Py_XDECREF(tb);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
PyAPI_FUNC(void) _PyErr_Fetch(
|
PyAPI_FUNC(void) _PyErr_Fetch(
|
||||||
PyThreadState *tstate,
|
PyThreadState *tstate,
|
||||||
|
@ -36,6 +50,9 @@ PyAPI_FUNC(void) _PyErr_SetObject(
|
||||||
PyObject *type,
|
PyObject *type,
|
||||||
PyObject *value);
|
PyObject *value);
|
||||||
|
|
||||||
|
PyAPI_FUNC(void) _PyErr_ChainStackItem(
|
||||||
|
_PyErr_StackItem *exc_state);
|
||||||
|
|
||||||
PyAPI_FUNC(void) _PyErr_Clear(PyThreadState *tstate);
|
PyAPI_FUNC(void) _PyErr_Clear(PyThreadState *tstate);
|
||||||
|
|
||||||
PyAPI_FUNC(void) _PyErr_SetNone(PyThreadState *tstate, PyObject *exception);
|
PyAPI_FUNC(void) _PyErr_SetNone(PyThreadState *tstate, PyObject *exception);
|
||||||
|
|
|
@ -52,6 +52,8 @@ class Future:
|
||||||
_loop = None
|
_loop = None
|
||||||
_source_traceback = None
|
_source_traceback = None
|
||||||
_cancel_message = None
|
_cancel_message = None
|
||||||
|
# A saved CancelledError for later chaining as an exception context.
|
||||||
|
_cancelled_exc = 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
|
||||||
|
@ -124,6 +126,21 @@ class Future:
|
||||||
raise RuntimeError("Future object is not initialized.")
|
raise RuntimeError("Future object is not initialized.")
|
||||||
return loop
|
return loop
|
||||||
|
|
||||||
|
def _make_cancelled_error(self):
|
||||||
|
"""Create the CancelledError to raise if the Future is cancelled.
|
||||||
|
|
||||||
|
This should only be called once when handling a cancellation since
|
||||||
|
it erases the saved context exception value.
|
||||||
|
"""
|
||||||
|
if self._cancel_message is None:
|
||||||
|
exc = exceptions.CancelledError()
|
||||||
|
else:
|
||||||
|
exc = exceptions.CancelledError(self._cancel_message)
|
||||||
|
exc.__context__ = self._cancelled_exc
|
||||||
|
# Remove the reference since we don't need this anymore.
|
||||||
|
self._cancelled_exc = None
|
||||||
|
return exc
|
||||||
|
|
||||||
def cancel(self, msg=None):
|
def cancel(self, msg=None):
|
||||||
"""Cancel the future and schedule callbacks.
|
"""Cancel the future and schedule callbacks.
|
||||||
|
|
||||||
|
@ -175,9 +192,8 @@ 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(
|
exc = self._make_cancelled_error()
|
||||||
'' if self._cancel_message is None else self._cancel_message)
|
raise exc
|
||||||
|
|
||||||
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
|
||||||
|
@ -194,8 +210,8 @@ class Future:
|
||||||
InvalidStateError.
|
InvalidStateError.
|
||||||
"""
|
"""
|
||||||
if self._state == _CANCELLED:
|
if self._state == _CANCELLED:
|
||||||
raise exceptions.CancelledError(
|
exc = self._make_cancelled_error()
|
||||||
'' if self._cancel_message is None else self._cancel_message)
|
raise exc
|
||||||
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
|
||||||
|
|
|
@ -270,8 +270,7 @@ 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 = self._make_cancelled_error()
|
||||||
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
|
||||||
|
@ -293,11 +292,9 @@ class Task(futures._PyFuture): # Inherit Python Task implementation
|
||||||
else:
|
else:
|
||||||
super().set_result(exc.value)
|
super().set_result(exc.value)
|
||||||
except exceptions.CancelledError as exc:
|
except exceptions.CancelledError as exc:
|
||||||
if exc.args:
|
# Save the original exception so we can chain it later.
|
||||||
cancel_msg = exc.args[0]
|
self._cancelled_exc = exc
|
||||||
else:
|
super().cancel() # I.e., Future.cancel(self).
|
||||||
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
|
||||||
|
@ -787,8 +784,7 @@ 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 = fut._make_cancelled_error()
|
||||||
if fut._cancel_message is None else fut._cancel_message)
|
|
||||||
outer.set_exception(exc)
|
outer.set_exception(exc)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
|
@ -804,9 +800,12 @@ def gather(*coros_or_futures, loop=None, return_exceptions=False):
|
||||||
|
|
||||||
for fut in children:
|
for fut in children:
|
||||||
if fut.cancelled():
|
if fut.cancelled():
|
||||||
# Check if 'fut' is cancelled first, as
|
# Check if 'fut' is cancelled first, as 'fut.exception()'
|
||||||
# 'fut.exception()' will *raise* a CancelledError
|
# will *raise* a CancelledError instead of returning it.
|
||||||
# instead of returning it.
|
# Also, since we're adding the exception return value
|
||||||
|
# to 'results' instead of raising it, don't bother
|
||||||
|
# setting __context__. This also lets us preserve
|
||||||
|
# calling '_make_cancelled_error()' at most once.
|
||||||
res = exceptions.CancelledError(
|
res = exceptions.CancelledError(
|
||||||
'' if fut._cancel_message is None else
|
'' if fut._cancel_message is None else
|
||||||
fut._cancel_message)
|
fut._cancel_message)
|
||||||
|
@ -820,8 +819,7 @@ 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.
|
||||||
exc = exceptions.CancelledError(''
|
exc = fut._make_cancelled_error()
|
||||||
if fut._cancel_message is None else fut._cancel_message)
|
|
||||||
outer.set_exception(exc)
|
outer.set_exception(exc)
|
||||||
else:
|
else:
|
||||||
outer.set_result(results)
|
outer.set_result(results)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import random
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
|
import traceback
|
||||||
import types
|
import types
|
||||||
import unittest
|
import unittest
|
||||||
import weakref
|
import weakref
|
||||||
|
@ -57,6 +58,22 @@ def format_coroutine(qualname, state, src, source_traceback, generator=False):
|
||||||
return 'coro=<%s() %s at %s>' % (qualname, state, src)
|
return 'coro=<%s() %s at %s>' % (qualname, state, src)
|
||||||
|
|
||||||
|
|
||||||
|
def get_innermost_context(exc):
|
||||||
|
"""
|
||||||
|
Return information about the innermost exception context in the chain.
|
||||||
|
"""
|
||||||
|
depth = 0
|
||||||
|
while True:
|
||||||
|
context = exc.__context__
|
||||||
|
if context is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
exc = context
|
||||||
|
depth += 1
|
||||||
|
|
||||||
|
return (type(exc), exc.args, depth)
|
||||||
|
|
||||||
|
|
||||||
class Dummy:
|
class Dummy:
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
@ -111,9 +128,10 @@ class BaseTaskTests:
|
||||||
self.assertEqual(t._cancel_message, None)
|
self.assertEqual(t._cancel_message, None)
|
||||||
|
|
||||||
t.cancel('my message')
|
t.cancel('my message')
|
||||||
|
self.assertEqual(t._cancel_message, 'my message')
|
||||||
|
|
||||||
with self.assertRaises(asyncio.CancelledError):
|
with self.assertRaises(asyncio.CancelledError):
|
||||||
self.loop.run_until_complete(t)
|
self.loop.run_until_complete(t)
|
||||||
self.assertEqual(t._cancel_message, 'my message')
|
|
||||||
|
|
||||||
def test_task_cancel_message_setter(self):
|
def test_task_cancel_message_setter(self):
|
||||||
async def coro():
|
async def coro():
|
||||||
|
@ -123,10 +141,8 @@ class BaseTaskTests:
|
||||||
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')
|
||||||
|
|
||||||
# Also check that the value is used for cancel().
|
|
||||||
with self.assertRaises(asyncio.CancelledError):
|
with self.assertRaises(asyncio.CancelledError):
|
||||||
self.loop.run_until_complete(t)
|
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:
|
||||||
|
@ -548,8 +564,8 @@ class BaseTaskTests:
|
||||||
def test_cancel_with_message_then_future_result(self):
|
def test_cancel_with_message_then_future_result(self):
|
||||||
# Test Future.result() after calling cancel() with a message.
|
# Test Future.result() after calling cancel() with a message.
|
||||||
cases = [
|
cases = [
|
||||||
((), ('',)),
|
((), ()),
|
||||||
((None,), ('',)),
|
((None,), ()),
|
||||||
(('my message',), ('my message',)),
|
(('my message',), ('my message',)),
|
||||||
# Non-string values should roundtrip.
|
# Non-string values should roundtrip.
|
||||||
((5,), (5,)),
|
((5,), (5,)),
|
||||||
|
@ -573,13 +589,17 @@ class BaseTaskTests:
|
||||||
with self.assertRaises(asyncio.CancelledError) as cm:
|
with self.assertRaises(asyncio.CancelledError) as cm:
|
||||||
loop.run_until_complete(task)
|
loop.run_until_complete(task)
|
||||||
exc = cm.exception
|
exc = cm.exception
|
||||||
self.assertEqual(exc.args, expected_args)
|
self.assertEqual(exc.args, ())
|
||||||
|
|
||||||
|
actual = get_innermost_context(exc)
|
||||||
|
self.assertEqual(actual,
|
||||||
|
(asyncio.CancelledError, expected_args, 2))
|
||||||
|
|
||||||
def test_cancel_with_message_then_future_exception(self):
|
def test_cancel_with_message_then_future_exception(self):
|
||||||
# Test Future.exception() after calling cancel() with a message.
|
# Test Future.exception() after calling cancel() with a message.
|
||||||
cases = [
|
cases = [
|
||||||
((), ('',)),
|
((), ()),
|
||||||
((None,), ('',)),
|
((None,), ()),
|
||||||
(('my message',), ('my message',)),
|
(('my message',), ('my message',)),
|
||||||
# Non-string values should roundtrip.
|
# Non-string values should roundtrip.
|
||||||
((5,), (5,)),
|
((5,), (5,)),
|
||||||
|
@ -603,7 +623,11 @@ class BaseTaskTests:
|
||||||
with self.assertRaises(asyncio.CancelledError) as cm:
|
with self.assertRaises(asyncio.CancelledError) as cm:
|
||||||
loop.run_until_complete(task)
|
loop.run_until_complete(task)
|
||||||
exc = cm.exception
|
exc = cm.exception
|
||||||
self.assertEqual(exc.args, expected_args)
|
self.assertEqual(exc.args, ())
|
||||||
|
|
||||||
|
actual = get_innermost_context(exc)
|
||||||
|
self.assertEqual(actual,
|
||||||
|
(asyncio.CancelledError, expected_args, 2))
|
||||||
|
|
||||||
def test_cancel_with_message_before_starting_task(self):
|
def test_cancel_with_message_before_starting_task(self):
|
||||||
loop = asyncio.new_event_loop()
|
loop = asyncio.new_event_loop()
|
||||||
|
@ -623,7 +647,11 @@ class BaseTaskTests:
|
||||||
with self.assertRaises(asyncio.CancelledError) as cm:
|
with self.assertRaises(asyncio.CancelledError) as cm:
|
||||||
loop.run_until_complete(task)
|
loop.run_until_complete(task)
|
||||||
exc = cm.exception
|
exc = cm.exception
|
||||||
self.assertEqual(exc.args, ('my message',))
|
self.assertEqual(exc.args, ())
|
||||||
|
|
||||||
|
actual = get_innermost_context(exc)
|
||||||
|
self.assertEqual(actual,
|
||||||
|
(asyncio.CancelledError, ('my message',), 2))
|
||||||
|
|
||||||
def test_cancel_yield(self):
|
def test_cancel_yield(self):
|
||||||
with self.assertWarns(DeprecationWarning):
|
with self.assertWarns(DeprecationWarning):
|
||||||
|
@ -805,6 +833,66 @@ class BaseTaskTests:
|
||||||
self.assertTrue(nested_task.cancelled())
|
self.assertTrue(nested_task.cancelled())
|
||||||
self.assertTrue(fut.cancelled())
|
self.assertTrue(fut.cancelled())
|
||||||
|
|
||||||
|
def assert_text_contains(self, text, substr):
|
||||||
|
if substr not in text:
|
||||||
|
raise RuntimeError(f'text {substr!r} not found in:\n>>>{text}<<<')
|
||||||
|
|
||||||
|
def test_cancel_traceback_for_future_result(self):
|
||||||
|
# When calling Future.result() on a cancelled task, check that the
|
||||||
|
# line of code that was interrupted is included in the traceback.
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
self.set_event_loop(loop)
|
||||||
|
|
||||||
|
async def nested():
|
||||||
|
# This will get cancelled immediately.
|
||||||
|
await asyncio.sleep(10)
|
||||||
|
|
||||||
|
async def coro():
|
||||||
|
task = self.new_task(loop, nested())
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
task.cancel()
|
||||||
|
await task # search target
|
||||||
|
|
||||||
|
task = self.new_task(loop, coro())
|
||||||
|
try:
|
||||||
|
loop.run_until_complete(task)
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
tb = traceback.format_exc()
|
||||||
|
self.assert_text_contains(tb, "await asyncio.sleep(10)")
|
||||||
|
# The intermediate await should also be included.
|
||||||
|
self.assert_text_contains(tb, "await task # search target")
|
||||||
|
else:
|
||||||
|
self.fail('CancelledError did not occur')
|
||||||
|
|
||||||
|
def test_cancel_traceback_for_future_exception(self):
|
||||||
|
# When calling Future.exception() on a cancelled task, check that the
|
||||||
|
# line of code that was interrupted is included in the traceback.
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
self.set_event_loop(loop)
|
||||||
|
|
||||||
|
async def nested():
|
||||||
|
# This will get cancelled immediately.
|
||||||
|
await asyncio.sleep(10)
|
||||||
|
|
||||||
|
async def coro():
|
||||||
|
task = self.new_task(loop, nested())
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
task.cancel()
|
||||||
|
done, pending = await asyncio.wait([task])
|
||||||
|
task.exception() # search target
|
||||||
|
|
||||||
|
task = self.new_task(loop, coro())
|
||||||
|
try:
|
||||||
|
loop.run_until_complete(task)
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
tb = traceback.format_exc()
|
||||||
|
self.assert_text_contains(tb, "await asyncio.sleep(10)")
|
||||||
|
# The intermediate await should also be included.
|
||||||
|
self.assert_text_contains(tb,
|
||||||
|
"task.exception() # search target")
|
||||||
|
else:
|
||||||
|
self.fail('CancelledError did not occur')
|
||||||
|
|
||||||
def test_stop_while_run_in_complete(self):
|
def test_stop_while_run_in_complete(self):
|
||||||
|
|
||||||
def gen():
|
def gen():
|
||||||
|
@ -2391,15 +2479,14 @@ class BaseTaskTests:
|
||||||
|
|
||||||
def test_cancel_gather_2(self):
|
def test_cancel_gather_2(self):
|
||||||
cases = [
|
cases = [
|
||||||
((), ('',)),
|
((), ()),
|
||||||
((None,), ('',)),
|
((None,), ()),
|
||||||
(('my message',), ('my message',)),
|
(('my message',), ('my message',)),
|
||||||
# Non-string values should roundtrip.
|
# Non-string values should roundtrip.
|
||||||
((5,), (5,)),
|
((5,), (5,)),
|
||||||
]
|
]
|
||||||
for cancel_args, expected_args in cases:
|
for cancel_args, expected_args in cases:
|
||||||
with self.subTest(cancel_args=cancel_args):
|
with self.subTest(cancel_args=cancel_args):
|
||||||
|
|
||||||
loop = asyncio.new_event_loop()
|
loop = asyncio.new_event_loop()
|
||||||
self.addCleanup(loop.close)
|
self.addCleanup(loop.close)
|
||||||
|
|
||||||
|
@ -2417,15 +2504,20 @@ class BaseTaskTests:
|
||||||
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)
|
qwe.cancel(*cancel_args)
|
||||||
try:
|
await qwe
|
||||||
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())
|
try:
|
||||||
|
loop.run_until_complete(main())
|
||||||
|
except asyncio.CancelledError as exc:
|
||||||
|
self.assertEqual(exc.args, ())
|
||||||
|
exc_type, exc_args, depth = get_innermost_context(exc)
|
||||||
|
self.assertEqual((exc_type, exc_args),
|
||||||
|
(asyncio.CancelledError, expected_args))
|
||||||
|
# The exact traceback seems to vary in CI.
|
||||||
|
self.assertIn(depth, (2, 3))
|
||||||
|
else:
|
||||||
|
self.fail('gather did not propagate the cancellation '
|
||||||
|
'request')
|
||||||
|
|
||||||
def test_exception_traceback(self):
|
def test_exception_traceback(self):
|
||||||
# See http://bugs.python.org/issue28843
|
# See http://bugs.python.org/issue28843
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
When a :class:`asyncio.Task` is cancelled, the exception traceback
|
||||||
|
now chains all the way back to where the task was first interrupted.
|
|
@ -1,4 +1,5 @@
|
||||||
#include "Python.h"
|
#include "Python.h"
|
||||||
|
#include "pycore_pyerrors.h" // _PyErr_ClearExcState()
|
||||||
#include <stddef.h> // offsetof()
|
#include <stddef.h> // offsetof()
|
||||||
|
|
||||||
|
|
||||||
|
@ -72,7 +73,8 @@ typedef enum {
|
||||||
int prefix##_log_tb; \
|
int prefix##_log_tb; \
|
||||||
int prefix##_blocking; \
|
int prefix##_blocking; \
|
||||||
PyObject *dict; \
|
PyObject *dict; \
|
||||||
PyObject *prefix##_weakreflist;
|
PyObject *prefix##_weakreflist; \
|
||||||
|
_PyErr_StackItem prefix##_cancelled_exc_state;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
FutureObj_HEAD(fut)
|
FutureObj_HEAD(fut)
|
||||||
|
@ -482,6 +484,7 @@ future_init(FutureObj *fut, PyObject *loop)
|
||||||
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->fut_cancel_msg);
|
||||||
|
_PyErr_ClearExcState(&fut->fut_cancelled_exc_state);
|
||||||
|
|
||||||
fut->fut_state = STATE_PENDING;
|
fut->fut_state = STATE_PENDING;
|
||||||
fut->fut_log_tb = 0;
|
fut->fut_log_tb = 0;
|
||||||
|
@ -601,9 +604,7 @@ create_cancelled_error(PyObject *msg)
|
||||||
{
|
{
|
||||||
PyObject *exc;
|
PyObject *exc;
|
||||||
if (msg == NULL || msg == Py_None) {
|
if (msg == NULL || msg == Py_None) {
|
||||||
msg = PyUnicode_FromString("");
|
exc = PyObject_CallNoArgs(asyncio_CancelledError);
|
||||||
exc = PyObject_CallOneArg(asyncio_CancelledError, msg);
|
|
||||||
Py_DECREF(msg);
|
|
||||||
} else {
|
} else {
|
||||||
exc = PyObject_CallOneArg(asyncio_CancelledError, msg);
|
exc = PyObject_CallOneArg(asyncio_CancelledError, msg);
|
||||||
}
|
}
|
||||||
|
@ -623,6 +624,7 @@ future_get_result(FutureObj *fut, PyObject **result)
|
||||||
{
|
{
|
||||||
if (fut->fut_state == STATE_CANCELLED) {
|
if (fut->fut_state == STATE_CANCELLED) {
|
||||||
set_cancelled_error(fut->fut_cancel_msg);
|
set_cancelled_error(fut->fut_cancel_msg);
|
||||||
|
_PyErr_ChainStackItem(&fut->fut_cancelled_exc_state);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -777,6 +779,7 @@ FutureObj_clear(FutureObj *fut)
|
||||||
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->fut_cancel_msg);
|
||||||
|
_PyErr_ClearExcState(&fut->fut_cancelled_exc_state);
|
||||||
Py_CLEAR(fut->dict);
|
Py_CLEAR(fut->dict);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -793,6 +796,12 @@ FutureObj_traverse(FutureObj *fut, visitproc visit, void *arg)
|
||||||
Py_VISIT(fut->fut_source_tb);
|
Py_VISIT(fut->fut_source_tb);
|
||||||
Py_VISIT(fut->fut_cancel_msg);
|
Py_VISIT(fut->fut_cancel_msg);
|
||||||
Py_VISIT(fut->dict);
|
Py_VISIT(fut->dict);
|
||||||
|
|
||||||
|
_PyErr_StackItem *exc_state = &fut->fut_cancelled_exc_state;
|
||||||
|
Py_VISIT(exc_state->exc_type);
|
||||||
|
Py_VISIT(exc_state->exc_value);
|
||||||
|
Py_VISIT(exc_state->exc_traceback);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -858,6 +867,7 @@ _asyncio_Future_exception_impl(FutureObj *self)
|
||||||
|
|
||||||
if (self->fut_state == STATE_CANCELLED) {
|
if (self->fut_state == STATE_CANCELLED) {
|
||||||
set_cancelled_error(self->fut_cancel_msg);
|
set_cancelled_error(self->fut_cancel_msg);
|
||||||
|
_PyErr_ChainStackItem(&self->fut_cancelled_exc_state);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1335,6 +1345,29 @@ FutureObj_get_state(FutureObj *fut, void *Py_UNUSED(ignored))
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*[clinic input]
|
||||||
|
_asyncio.Future._make_cancelled_error
|
||||||
|
|
||||||
|
Create the CancelledError to raise if the Future is cancelled.
|
||||||
|
|
||||||
|
This should only be called once when handling a cancellation since
|
||||||
|
it erases the context exception value.
|
||||||
|
[clinic start generated code]*/
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_asyncio_Future__make_cancelled_error_impl(FutureObj *self)
|
||||||
|
/*[clinic end generated code: output=a5df276f6c1213de input=ac6effe4ba795ecc]*/
|
||||||
|
{
|
||||||
|
PyObject *exc = create_cancelled_error(self->fut_cancel_msg);
|
||||||
|
_PyErr_StackItem *exc_state = &self->fut_cancelled_exc_state;
|
||||||
|
/* Transfer ownership of exc_value from exc_state to exc since we are
|
||||||
|
done with it. */
|
||||||
|
PyException_SetContext(exc, exc_state->exc_value);
|
||||||
|
exc_state->exc_value = NULL;
|
||||||
|
|
||||||
|
return exc;
|
||||||
|
}
|
||||||
|
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
_asyncio.Future._repr_info
|
_asyncio.Future._repr_info
|
||||||
[clinic start generated code]*/
|
[clinic start generated code]*/
|
||||||
|
@ -1461,6 +1494,7 @@ static PyMethodDef FutureType_methods[] = {
|
||||||
_ASYNCIO_FUTURE_CANCELLED_METHODDEF
|
_ASYNCIO_FUTURE_CANCELLED_METHODDEF
|
||||||
_ASYNCIO_FUTURE_DONE_METHODDEF
|
_ASYNCIO_FUTURE_DONE_METHODDEF
|
||||||
_ASYNCIO_FUTURE_GET_LOOP_METHODDEF
|
_ASYNCIO_FUTURE_GET_LOOP_METHODDEF
|
||||||
|
_ASYNCIO_FUTURE__MAKE_CANCELLED_ERROR_METHODDEF
|
||||||
_ASYNCIO_FUTURE__REPR_INFO_METHODDEF
|
_ASYNCIO_FUTURE__REPR_INFO_METHODDEF
|
||||||
{"__class_getitem__", future_cls_getitem, METH_O|METH_CLASS, NULL},
|
{"__class_getitem__", future_cls_getitem, METH_O|METH_CLASS, NULL},
|
||||||
{NULL, NULL} /* Sentinel */
|
{NULL, NULL} /* Sentinel */
|
||||||
|
@ -2232,6 +2266,24 @@ _asyncio_Task_all_tasks_impl(PyTypeObject *type, PyObject *loop)
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*[clinic input]
|
||||||
|
_asyncio.Task._make_cancelled_error
|
||||||
|
|
||||||
|
Create the CancelledError to raise if the Task is cancelled.
|
||||||
|
|
||||||
|
This should only be called once when handling a cancellation since
|
||||||
|
it erases the context exception value.
|
||||||
|
[clinic start generated code]*/
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_asyncio_Task__make_cancelled_error_impl(TaskObj *self)
|
||||||
|
/*[clinic end generated code: output=55a819e8b4276fab input=52c0e32de8e2f840]*/
|
||||||
|
{
|
||||||
|
FutureObj *fut = (FutureObj*)self;
|
||||||
|
return _asyncio_Future__make_cancelled_error_impl(fut);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
_asyncio.Task._repr_info
|
_asyncio.Task._repr_info
|
||||||
[clinic start generated code]*/
|
[clinic start generated code]*/
|
||||||
|
@ -2539,6 +2591,7 @@ static PyMethodDef TaskType_methods[] = {
|
||||||
_ASYNCIO_TASK_CANCEL_METHODDEF
|
_ASYNCIO_TASK_CANCEL_METHODDEF
|
||||||
_ASYNCIO_TASK_GET_STACK_METHODDEF
|
_ASYNCIO_TASK_GET_STACK_METHODDEF
|
||||||
_ASYNCIO_TASK_PRINT_STACK_METHODDEF
|
_ASYNCIO_TASK_PRINT_STACK_METHODDEF
|
||||||
|
_ASYNCIO_TASK__MAKE_CANCELLED_ERROR_METHODDEF
|
||||||
_ASYNCIO_TASK__REPR_INFO_METHODDEF
|
_ASYNCIO_TASK__REPR_INFO_METHODDEF
|
||||||
_ASYNCIO_TASK_GET_NAME_METHODDEF
|
_ASYNCIO_TASK_GET_NAME_METHODDEF
|
||||||
_ASYNCIO_TASK_SET_NAME_METHODDEF
|
_ASYNCIO_TASK_SET_NAME_METHODDEF
|
||||||
|
@ -2754,24 +2807,13 @@ task_step_impl(TaskObj *task, PyObject *exc)
|
||||||
/* CancelledError */
|
/* CancelledError */
|
||||||
PyErr_Fetch(&et, &ev, &tb);
|
PyErr_Fetch(&et, &ev, &tb);
|
||||||
|
|
||||||
PyObject *cancel_msg;
|
FutureObj *fut = (FutureObj*)task;
|
||||||
if (ev != NULL && PyExceptionInstance_Check(ev)) {
|
_PyErr_StackItem *exc_state = &fut->fut_cancelled_exc_state;
|
||||||
PyObject *exc_args = ((PyBaseExceptionObject*)ev)->args;
|
exc_state->exc_type = et;
|
||||||
Py_ssize_t size = PyTuple_GET_SIZE(exc_args);
|
exc_state->exc_value = ev;
|
||||||
if (size > 0) {
|
exc_state->exc_traceback = tb;
|
||||||
cancel_msg = PyTuple_GET_ITEM(exc_args, 0);
|
|
||||||
} else {
|
|
||||||
cancel_msg = NULL;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cancel_msg = ev;
|
|
||||||
}
|
|
||||||
|
|
||||||
Py_DECREF(et);
|
return future_cancel(fut, NULL);
|
||||||
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() */
|
||||||
|
|
|
@ -271,6 +271,27 @@ _asyncio_Future_get_loop(FutureObj *self, PyObject *Py_UNUSED(ignored))
|
||||||
return _asyncio_Future_get_loop_impl(self);
|
return _asyncio_Future_get_loop_impl(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(_asyncio_Future__make_cancelled_error__doc__,
|
||||||
|
"_make_cancelled_error($self, /)\n"
|
||||||
|
"--\n"
|
||||||
|
"\n"
|
||||||
|
"Create the CancelledError to raise if the Future is cancelled.\n"
|
||||||
|
"\n"
|
||||||
|
"This should only be called once when handling a cancellation since\n"
|
||||||
|
"it erases the context exception value.");
|
||||||
|
|
||||||
|
#define _ASYNCIO_FUTURE__MAKE_CANCELLED_ERROR_METHODDEF \
|
||||||
|
{"_make_cancelled_error", (PyCFunction)_asyncio_Future__make_cancelled_error, METH_NOARGS, _asyncio_Future__make_cancelled_error__doc__},
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_asyncio_Future__make_cancelled_error_impl(FutureObj *self);
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_asyncio_Future__make_cancelled_error(FutureObj *self, PyObject *Py_UNUSED(ignored))
|
||||||
|
{
|
||||||
|
return _asyncio_Future__make_cancelled_error_impl(self);
|
||||||
|
}
|
||||||
|
|
||||||
PyDoc_STRVAR(_asyncio_Future__repr_info__doc__,
|
PyDoc_STRVAR(_asyncio_Future__repr_info__doc__,
|
||||||
"_repr_info($self, /)\n"
|
"_repr_info($self, /)\n"
|
||||||
"--\n"
|
"--\n"
|
||||||
|
@ -414,6 +435,27 @@ exit:
|
||||||
return return_value;
|
return return_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(_asyncio_Task__make_cancelled_error__doc__,
|
||||||
|
"_make_cancelled_error($self, /)\n"
|
||||||
|
"--\n"
|
||||||
|
"\n"
|
||||||
|
"Create the CancelledError to raise if the Task is cancelled.\n"
|
||||||
|
"\n"
|
||||||
|
"This should only be called once when handling a cancellation since\n"
|
||||||
|
"it erases the context exception value.");
|
||||||
|
|
||||||
|
#define _ASYNCIO_TASK__MAKE_CANCELLED_ERROR_METHODDEF \
|
||||||
|
{"_make_cancelled_error", (PyCFunction)_asyncio_Task__make_cancelled_error, METH_NOARGS, _asyncio_Task__make_cancelled_error__doc__},
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_asyncio_Task__make_cancelled_error_impl(TaskObj *self);
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_asyncio_Task__make_cancelled_error(TaskObj *self, PyObject *Py_UNUSED(ignored))
|
||||||
|
{
|
||||||
|
return _asyncio_Task__make_cancelled_error_impl(self);
|
||||||
|
}
|
||||||
|
|
||||||
PyDoc_STRVAR(_asyncio_Task__repr_info__doc__,
|
PyDoc_STRVAR(_asyncio_Task__repr_info__doc__,
|
||||||
"_repr_info($self, /)\n"
|
"_repr_info($self, /)\n"
|
||||||
"--\n"
|
"--\n"
|
||||||
|
@ -870,4 +912,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=6ed4cfda8fc516ad input=a9049054013a1b77]*/
|
/*[clinic end generated code: output=0e5c1eb8b692977b input=a9049054013a1b77]*/
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "Python.h"
|
#include "Python.h"
|
||||||
#include "pycore_ceval.h" // _PyEval_EvalFrame()
|
#include "pycore_ceval.h" // _PyEval_EvalFrame()
|
||||||
#include "pycore_object.h"
|
#include "pycore_object.h"
|
||||||
|
#include "pycore_pyerrors.h" // _PyErr_ClearExcState()
|
||||||
#include "pycore_pystate.h" // _PyThreadState_GET()
|
#include "pycore_pystate.h" // _PyThreadState_GET()
|
||||||
#include "frameobject.h"
|
#include "frameobject.h"
|
||||||
#include "structmember.h" // PyMemberDef
|
#include "structmember.h" // PyMemberDef
|
||||||
|
@ -99,21 +100,6 @@ _PyGen_Finalize(PyObject *self)
|
||||||
PyErr_Restore(error_type, error_value, error_traceback);
|
PyErr_Restore(error_type, error_value, error_traceback);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
|
||||||
exc_state_clear(_PyErr_StackItem *exc_state)
|
|
||||||
{
|
|
||||||
PyObject *t, *v, *tb;
|
|
||||||
t = exc_state->exc_type;
|
|
||||||
v = exc_state->exc_value;
|
|
||||||
tb = exc_state->exc_traceback;
|
|
||||||
exc_state->exc_type = NULL;
|
|
||||||
exc_state->exc_value = NULL;
|
|
||||||
exc_state->exc_traceback = NULL;
|
|
||||||
Py_XDECREF(t);
|
|
||||||
Py_XDECREF(v);
|
|
||||||
Py_XDECREF(tb);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
gen_dealloc(PyGenObject *gen)
|
gen_dealloc(PyGenObject *gen)
|
||||||
{
|
{
|
||||||
|
@ -146,7 +132,7 @@ gen_dealloc(PyGenObject *gen)
|
||||||
Py_CLEAR(gen->gi_code);
|
Py_CLEAR(gen->gi_code);
|
||||||
Py_CLEAR(gen->gi_name);
|
Py_CLEAR(gen->gi_name);
|
||||||
Py_CLEAR(gen->gi_qualname);
|
Py_CLEAR(gen->gi_qualname);
|
||||||
exc_state_clear(&gen->gi_exc_state);
|
_PyErr_ClearExcState(&gen->gi_exc_state);
|
||||||
PyObject_GC_Del(gen);
|
PyObject_GC_Del(gen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,7 +272,7 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing)
|
||||||
if (!result || f->f_stacktop == NULL) {
|
if (!result || f->f_stacktop == NULL) {
|
||||||
/* generator can't be rerun, so release the frame */
|
/* generator can't be rerun, so release the frame */
|
||||||
/* first clean reference cycle through stored exception traceback */
|
/* first clean reference cycle through stored exception traceback */
|
||||||
exc_state_clear(&gen->gi_exc_state);
|
_PyErr_ClearExcState(&gen->gi_exc_state);
|
||||||
gen->gi_frame->f_gen = NULL;
|
gen->gi_frame->f_gen = NULL;
|
||||||
gen->gi_frame = NULL;
|
gen->gi_frame = NULL;
|
||||||
Py_DECREF(f);
|
Py_DECREF(f);
|
||||||
|
|
|
@ -512,6 +512,20 @@ _PyErr_ChainExceptions(PyObject *exc, PyObject *val, PyObject *tb)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
_PyErr_ChainStackItem(_PyErr_StackItem *exc_state)
|
||||||
|
{
|
||||||
|
if (exc_state->exc_type == NULL || exc_state->exc_type == Py_None) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Py_INCREF(exc_state->exc_type);
|
||||||
|
Py_XINCREF(exc_state->exc_value);
|
||||||
|
Py_XINCREF(exc_state->exc_traceback);
|
||||||
|
_PyErr_ChainExceptions(exc_state->exc_type,
|
||||||
|
exc_state->exc_value,
|
||||||
|
exc_state->exc_traceback);
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
_PyErr_FormatVFromCause(PyThreadState *tstate, PyObject *exception,
|
_PyErr_FormatVFromCause(PyThreadState *tstate, PyObject *exception,
|
||||||
const char *format, va_list vargs)
|
const char *format, va_list vargs)
|
||||||
|
|
3
setup.py
3
setup.py
|
@ -853,7 +853,8 @@ class PyBuildExt(build_ext):
|
||||||
# _opcode module
|
# _opcode module
|
||||||
self.add(Extension('_opcode', ['_opcode.c']))
|
self.add(Extension('_opcode', ['_opcode.c']))
|
||||||
# asyncio speedups
|
# asyncio speedups
|
||||||
self.add(Extension("_asyncio", ["_asynciomodule.c"]))
|
self.add(Extension("_asyncio", ["_asynciomodule.c"],
|
||||||
|
extra_compile_args=['-DPy_BUILD_CORE_MODULE']))
|
||||||
# _abc speedups
|
# _abc speedups
|
||||||
self.add(Extension("_abc", ["_abc.c"]))
|
self.add(Extension("_abc", ["_abc.c"]))
|
||||||
# _queue module
|
# _queue module
|
||||||
|
|
Loading…
Reference in New Issue