bpo-32591: Add native coroutine origin tracking (#5250)
* Add coro.cr_origin and sys.set_coroutine_origin_tracking_depth * Use coroutine origin information in the unawaited coroutine warning * Stop using set_coroutine_wrapper in asyncio debug mode * In BaseEventLoop.set_debug, enable debugging in the correct thread
This commit is contained in:
parent
1211c9a989
commit
fc2f407829
|
@ -34,6 +34,9 @@ provided as convenient choices for the second argument to :func:`getmembers`.
|
||||||
They also help you determine when you can expect to find the following special
|
They also help you determine when you can expect to find the following special
|
||||||
attributes:
|
attributes:
|
||||||
|
|
||||||
|
.. this function name is too big to fit in the ascii-art table below
|
||||||
|
.. |coroutine-origin-link| replace:: :func:`sys.set_coroutine_origin_tracking_depth`
|
||||||
|
|
||||||
+-----------+-------------------+---------------------------+
|
+-----------+-------------------+---------------------------+
|
||||||
| Type | Attribute | Description |
|
| Type | Attribute | Description |
|
||||||
+===========+===================+===========================+
|
+===========+===================+===========================+
|
||||||
|
@ -215,6 +218,10 @@ attributes:
|
||||||
+-----------+-------------------+---------------------------+
|
+-----------+-------------------+---------------------------+
|
||||||
| | cr_code | code |
|
| | cr_code | code |
|
||||||
+-----------+-------------------+---------------------------+
|
+-----------+-------------------+---------------------------+
|
||||||
|
| | cr_origin | where coroutine was |
|
||||||
|
| | | created, or ``None``. See |
|
||||||
|
| | | |coroutine-origin-link| |
|
||||||
|
+-----------+-------------------+---------------------------+
|
||||||
| builtin | __doc__ | documentation string |
|
| builtin | __doc__ | documentation string |
|
||||||
+-----------+-------------------+---------------------------+
|
+-----------+-------------------+---------------------------+
|
||||||
| | __name__ | original name of this |
|
| | __name__ | original name of this |
|
||||||
|
@ -234,6 +241,9 @@ attributes:
|
||||||
The ``__name__`` attribute of generators is now set from the function
|
The ``__name__`` attribute of generators is now set from the function
|
||||||
name, instead of the code name, and it can now be modified.
|
name, instead of the code name, and it can now be modified.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.7
|
||||||
|
|
||||||
|
Add ``cr_origin`` attribute to coroutines.
|
||||||
|
|
||||||
.. function:: getmembers(object[, predicate])
|
.. function:: getmembers(object[, predicate])
|
||||||
|
|
||||||
|
|
|
@ -675,6 +675,18 @@ always available.
|
||||||
for details.)
|
for details.)
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: get_coroutine_origin_tracking_depth()
|
||||||
|
|
||||||
|
Get the current coroutine origin tracking depth, as set by
|
||||||
|
func:`set_coroutine_origin_tracking_depth`.
|
||||||
|
|
||||||
|
.. versionadded:: 3.7
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
This function has been added on a provisional basis (see :pep:`411`
|
||||||
|
for details.) Use it only for debugging purposes.
|
||||||
|
|
||||||
|
|
||||||
.. function:: get_coroutine_wrapper()
|
.. function:: get_coroutine_wrapper()
|
||||||
|
|
||||||
Returns ``None``, or a wrapper set by :func:`set_coroutine_wrapper`.
|
Returns ``None``, or a wrapper set by :func:`set_coroutine_wrapper`.
|
||||||
|
@ -686,6 +698,10 @@ always available.
|
||||||
This function has been added on a provisional basis (see :pep:`411`
|
This function has been added on a provisional basis (see :pep:`411`
|
||||||
for details.) Use it only for debugging purposes.
|
for details.) Use it only for debugging purposes.
|
||||||
|
|
||||||
|
.. deprecated:: 3.7
|
||||||
|
The coroutine wrapper functionality has been deprecated, and
|
||||||
|
will be removed in 3.8. See :issue:`32591` for details.
|
||||||
|
|
||||||
|
|
||||||
.. data:: hash_info
|
.. data:: hash_info
|
||||||
|
|
||||||
|
@ -1212,6 +1228,26 @@ always available.
|
||||||
This function has been added on a provisional basis (see :pep:`411`
|
This function has been added on a provisional basis (see :pep:`411`
|
||||||
for details.)
|
for details.)
|
||||||
|
|
||||||
|
.. function:: set_coroutine_origin_tracking_depth(depth)
|
||||||
|
|
||||||
|
Allows enabling or disabling coroutine origin tracking. When
|
||||||
|
enabled, the ``cr_origin`` attribute on coroutine objects will
|
||||||
|
contain a tuple of (filename, line number, function name) tuples
|
||||||
|
describing the traceback where the coroutine object was created,
|
||||||
|
with the most recent call first. When disabled, ``cr_origin`` will
|
||||||
|
be None.
|
||||||
|
|
||||||
|
To enable, pass a *depth* value greater than zero; this sets the
|
||||||
|
number of frames whose information will be captured. To disable,
|
||||||
|
pass set *depth* to zero.
|
||||||
|
|
||||||
|
This setting is thread-specific.
|
||||||
|
|
||||||
|
.. versionadded:: 3.7
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
This function has been added on a provisional basis (see :pep:`411`
|
||||||
|
for details.) Use it only for debugging purposes.
|
||||||
|
|
||||||
.. function:: set_coroutine_wrapper(wrapper)
|
.. function:: set_coroutine_wrapper(wrapper)
|
||||||
|
|
||||||
|
@ -1252,6 +1288,10 @@ always available.
|
||||||
This function has been added on a provisional basis (see :pep:`411`
|
This function has been added on a provisional basis (see :pep:`411`
|
||||||
for details.) Use it only for debugging purposes.
|
for details.) Use it only for debugging purposes.
|
||||||
|
|
||||||
|
.. deprecated:: 3.7
|
||||||
|
The coroutine wrapper functionality has been deprecated, and
|
||||||
|
will be removed in 3.8. See :issue:`32591` for details.
|
||||||
|
|
||||||
.. function:: _enablelegacywindowsfsencoding()
|
.. function:: _enablelegacywindowsfsencoding()
|
||||||
|
|
||||||
Changes the default filesystem encoding and errors mode to 'mbcs' and
|
Changes the default filesystem encoding and errors mode to 'mbcs' and
|
||||||
|
|
|
@ -510,6 +510,9 @@ sys
|
||||||
|
|
||||||
Added :attr:`sys.flags.dev_mode` flag for the new development mode.
|
Added :attr:`sys.flags.dev_mode` flag for the new development mode.
|
||||||
|
|
||||||
|
Deprecated :func:`sys.set_coroutine_wrapper` and
|
||||||
|
:func:`sys.get_coroutine_wrapper`.
|
||||||
|
|
||||||
time
|
time
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,8 @@ PyAPI_FUNC(PyObject *) PyEval_CallMethod(PyObject *obj,
|
||||||
#ifndef Py_LIMITED_API
|
#ifndef Py_LIMITED_API
|
||||||
PyAPI_FUNC(void) PyEval_SetProfile(Py_tracefunc, PyObject *);
|
PyAPI_FUNC(void) PyEval_SetProfile(Py_tracefunc, PyObject *);
|
||||||
PyAPI_FUNC(void) PyEval_SetTrace(Py_tracefunc, PyObject *);
|
PyAPI_FUNC(void) PyEval_SetTrace(Py_tracefunc, PyObject *);
|
||||||
|
PyAPI_FUNC(void) _PyEval_SetCoroutineOriginTrackingDepth(int new_depth);
|
||||||
|
PyAPI_FUNC(int) _PyEval_GetCoroutineOriginTrackingDepth(void);
|
||||||
PyAPI_FUNC(void) _PyEval_SetCoroutineWrapper(PyObject *);
|
PyAPI_FUNC(void) _PyEval_SetCoroutineWrapper(PyObject *);
|
||||||
PyAPI_FUNC(PyObject *) _PyEval_GetCoroutineWrapper(void);
|
PyAPI_FUNC(PyObject *) _PyEval_GetCoroutineWrapper(void);
|
||||||
PyAPI_FUNC(void) _PyEval_SetAsyncGenFirstiter(PyObject *);
|
PyAPI_FUNC(void) _PyEval_SetAsyncGenFirstiter(PyObject *);
|
||||||
|
|
|
@ -51,6 +51,7 @@ PyAPI_FUNC(void) _PyGen_Finalize(PyObject *self);
|
||||||
#ifndef Py_LIMITED_API
|
#ifndef Py_LIMITED_API
|
||||||
typedef struct {
|
typedef struct {
|
||||||
_PyGenObject_HEAD(cr)
|
_PyGenObject_HEAD(cr)
|
||||||
|
PyObject *cr_origin;
|
||||||
} PyCoroObject;
|
} PyCoroObject;
|
||||||
|
|
||||||
PyAPI_DATA(PyTypeObject) PyCoro_Type;
|
PyAPI_DATA(PyTypeObject) PyCoro_Type;
|
||||||
|
|
|
@ -262,6 +262,8 @@ typedef struct _ts {
|
||||||
void (*on_delete)(void *);
|
void (*on_delete)(void *);
|
||||||
void *on_delete_data;
|
void *on_delete_data;
|
||||||
|
|
||||||
|
int coroutine_origin_tracking_depth;
|
||||||
|
|
||||||
PyObject *coroutine_wrapper;
|
PyObject *coroutine_wrapper;
|
||||||
int in_coroutine_wrapper;
|
int in_coroutine_wrapper;
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,10 @@ PyErr_WarnExplicitFormat(PyObject *category,
|
||||||
#define PyErr_Warn(category, msg) PyErr_WarnEx(category, msg, 1)
|
#define PyErr_Warn(category, msg) PyErr_WarnEx(category, msg, 1)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef Py_LIMITED_API
|
||||||
|
void _PyErr_WarnUnawaitedCoroutine(PyObject *coro);
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -34,6 +34,7 @@ try:
|
||||||
except ImportError: # pragma: no cover
|
except ImportError: # pragma: no cover
|
||||||
ssl = None
|
ssl = None
|
||||||
|
|
||||||
|
from . import constants
|
||||||
from . import coroutines
|
from . import coroutines
|
||||||
from . import events
|
from . import events
|
||||||
from . import futures
|
from . import futures
|
||||||
|
@ -224,7 +225,8 @@ class BaseEventLoop(events.AbstractEventLoop):
|
||||||
self.slow_callback_duration = 0.1
|
self.slow_callback_duration = 0.1
|
||||||
self._current_handle = None
|
self._current_handle = None
|
||||||
self._task_factory = None
|
self._task_factory = None
|
||||||
self._coroutine_wrapper_set = False
|
self._coroutine_origin_tracking_enabled = False
|
||||||
|
self._coroutine_origin_tracking_saved_depth = None
|
||||||
|
|
||||||
if hasattr(sys, 'get_asyncgen_hooks'):
|
if hasattr(sys, 'get_asyncgen_hooks'):
|
||||||
# Python >= 3.6
|
# Python >= 3.6
|
||||||
|
@ -382,7 +384,7 @@ class BaseEventLoop(events.AbstractEventLoop):
|
||||||
if events._get_running_loop() is not None:
|
if events._get_running_loop() is not None:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
'Cannot run the event loop while another loop is running')
|
'Cannot run the event loop while another loop is running')
|
||||||
self._set_coroutine_wrapper(self._debug)
|
self._set_coroutine_origin_tracking(self._debug)
|
||||||
self._thread_id = threading.get_ident()
|
self._thread_id = threading.get_ident()
|
||||||
if self._asyncgens is not None:
|
if self._asyncgens is not None:
|
||||||
old_agen_hooks = sys.get_asyncgen_hooks()
|
old_agen_hooks = sys.get_asyncgen_hooks()
|
||||||
|
@ -398,7 +400,7 @@ class BaseEventLoop(events.AbstractEventLoop):
|
||||||
self._stopping = False
|
self._stopping = False
|
||||||
self._thread_id = None
|
self._thread_id = None
|
||||||
events._set_running_loop(None)
|
events._set_running_loop(None)
|
||||||
self._set_coroutine_wrapper(False)
|
self._set_coroutine_origin_tracking(False)
|
||||||
if self._asyncgens is not None:
|
if self._asyncgens is not None:
|
||||||
sys.set_asyncgen_hooks(*old_agen_hooks)
|
sys.set_asyncgen_hooks(*old_agen_hooks)
|
||||||
|
|
||||||
|
@ -1531,39 +1533,20 @@ class BaseEventLoop(events.AbstractEventLoop):
|
||||||
handle._run()
|
handle._run()
|
||||||
handle = None # Needed to break cycles when an exception occurs.
|
handle = None # Needed to break cycles when an exception occurs.
|
||||||
|
|
||||||
def _set_coroutine_wrapper(self, enabled):
|
def _set_coroutine_origin_tracking(self, enabled):
|
||||||
try:
|
if bool(enabled) == bool(self._coroutine_origin_tracking_enabled):
|
||||||
set_wrapper = sys.set_coroutine_wrapper
|
|
||||||
get_wrapper = sys.get_coroutine_wrapper
|
|
||||||
except AttributeError:
|
|
||||||
return
|
return
|
||||||
|
|
||||||
enabled = bool(enabled)
|
|
||||||
if self._coroutine_wrapper_set == enabled:
|
|
||||||
return
|
|
||||||
|
|
||||||
wrapper = coroutines.debug_wrapper
|
|
||||||
current_wrapper = get_wrapper()
|
|
||||||
|
|
||||||
if enabled:
|
if enabled:
|
||||||
if current_wrapper not in (None, wrapper):
|
self._coroutine_origin_tracking_saved_depth = (
|
||||||
warnings.warn(
|
sys.get_coroutine_origin_tracking_depth())
|
||||||
f"loop.set_debug(True): cannot set debug coroutine "
|
sys.set_coroutine_origin_tracking_depth(
|
||||||
f"wrapper; another wrapper is already set "
|
constants.DEBUG_STACK_DEPTH)
|
||||||
f"{current_wrapper!r}",
|
|
||||||
RuntimeWarning)
|
|
||||||
else:
|
else:
|
||||||
set_wrapper(wrapper)
|
sys.set_coroutine_origin_tracking_depth(
|
||||||
self._coroutine_wrapper_set = True
|
self._coroutine_origin_tracking_saved_depth)
|
||||||
else:
|
|
||||||
if current_wrapper not in (None, wrapper):
|
self._coroutine_origin_tracking_enabled = enabled
|
||||||
warnings.warn(
|
|
||||||
f"loop.set_debug(False): cannot unset debug coroutine "
|
|
||||||
f"wrapper; another wrapper was set {current_wrapper!r}",
|
|
||||||
RuntimeWarning)
|
|
||||||
else:
|
|
||||||
set_wrapper(None)
|
|
||||||
self._coroutine_wrapper_set = False
|
|
||||||
|
|
||||||
def get_debug(self):
|
def get_debug(self):
|
||||||
return self._debug
|
return self._debug
|
||||||
|
@ -1572,4 +1555,4 @@ class BaseEventLoop(events.AbstractEventLoop):
|
||||||
self._debug = enabled
|
self._debug = enabled
|
||||||
|
|
||||||
if self.is_running():
|
if self.is_running():
|
||||||
self._set_coroutine_wrapper(enabled)
|
self.call_soon_threadsafe(self._set_coroutine_origin_tracking, enabled)
|
||||||
|
|
|
@ -32,14 +32,6 @@ def _is_debug_mode():
|
||||||
_DEBUG = _is_debug_mode()
|
_DEBUG = _is_debug_mode()
|
||||||
|
|
||||||
|
|
||||||
def debug_wrapper(gen):
|
|
||||||
# This function is called from 'sys.set_coroutine_wrapper'.
|
|
||||||
# We only wrap here coroutines defined via 'async def' syntax.
|
|
||||||
# Generator-based coroutines are wrapped in @coroutine
|
|
||||||
# decorator.
|
|
||||||
return CoroWrapper(gen, None)
|
|
||||||
|
|
||||||
|
|
||||||
class CoroWrapper:
|
class CoroWrapper:
|
||||||
# Wrapper for coroutine object in _DEBUG mode.
|
# Wrapper for coroutine object in _DEBUG mode.
|
||||||
|
|
||||||
|
@ -87,39 +79,16 @@ class CoroWrapper:
|
||||||
return self.gen.gi_code
|
return self.gen.gi_code
|
||||||
|
|
||||||
def __await__(self):
|
def __await__(self):
|
||||||
cr_await = getattr(self.gen, 'cr_await', None)
|
|
||||||
if cr_await is not None:
|
|
||||||
raise RuntimeError(
|
|
||||||
f"Cannot await on coroutine {self.gen!r} while it's "
|
|
||||||
f"awaiting for {cr_await!r}")
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def gi_yieldfrom(self):
|
def gi_yieldfrom(self):
|
||||||
return self.gen.gi_yieldfrom
|
return self.gen.gi_yieldfrom
|
||||||
|
|
||||||
@property
|
|
||||||
def cr_await(self):
|
|
||||||
return self.gen.cr_await
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cr_running(self):
|
|
||||||
return self.gen.cr_running
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cr_code(self):
|
|
||||||
return self.gen.cr_code
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cr_frame(self):
|
|
||||||
return self.gen.cr_frame
|
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
# Be careful accessing self.gen.frame -- self.gen might not exist.
|
# Be careful accessing self.gen.frame -- self.gen might not exist.
|
||||||
gen = getattr(self, 'gen', None)
|
gen = getattr(self, 'gen', None)
|
||||||
frame = getattr(gen, 'gi_frame', None)
|
frame = getattr(gen, 'gi_frame', None)
|
||||||
if frame is None:
|
|
||||||
frame = getattr(gen, 'cr_frame', None)
|
|
||||||
if frame is not None and frame.f_lasti == -1:
|
if frame is not None and frame.f_lasti == -1:
|
||||||
msg = f'{self!r} was never yielded from'
|
msg = f'{self!r} was never yielded from'
|
||||||
tb = getattr(self, '_source_traceback', ())
|
tb = getattr(self, '_source_traceback', ())
|
||||||
|
@ -141,8 +110,6 @@ def coroutine(func):
|
||||||
if inspect.iscoroutinefunction(func):
|
if inspect.iscoroutinefunction(func):
|
||||||
# In Python 3.5 that's all we need to do for coroutines
|
# In Python 3.5 that's all we need to do for coroutines
|
||||||
# defined with "async def".
|
# defined with "async def".
|
||||||
# Wrapping in CoroWrapper will happen via
|
|
||||||
# 'sys.set_coroutine_wrapper' function.
|
|
||||||
return func
|
return func
|
||||||
|
|
||||||
if inspect.isgeneratorfunction(func):
|
if inspect.isgeneratorfunction(func):
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
"""Tests support for new syntax introduced by PEP 492."""
|
"""Tests support for new syntax introduced by PEP 492."""
|
||||||
|
|
||||||
|
import sys
|
||||||
import types
|
import types
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
@ -148,35 +149,14 @@ class CoroutineTests(BaseTest):
|
||||||
data = self.loop.run_until_complete(foo())
|
data = self.loop.run_until_complete(foo())
|
||||||
self.assertEqual(data, 'spam')
|
self.assertEqual(data, 'spam')
|
||||||
|
|
||||||
@mock.patch('asyncio.coroutines.logger')
|
def test_debug_mode_manages_coroutine_origin_tracking(self):
|
||||||
def test_async_def_wrapped(self, m_log):
|
|
||||||
async def foo():
|
|
||||||
pass
|
|
||||||
async def start():
|
async def start():
|
||||||
foo_coro = foo()
|
self.assertTrue(sys.get_coroutine_origin_tracking_depth() > 0)
|
||||||
self.assertRegex(
|
|
||||||
repr(foo_coro),
|
|
||||||
r'<CoroWrapper .*\.foo\(\) running at .*pep492.*>')
|
|
||||||
|
|
||||||
with support.check_warnings((r'.*foo.*was never',
|
|
||||||
RuntimeWarning)):
|
|
||||||
foo_coro = None
|
|
||||||
support.gc_collect()
|
|
||||||
self.assertTrue(m_log.error.called)
|
|
||||||
message = m_log.error.call_args[0][0]
|
|
||||||
self.assertRegex(message,
|
|
||||||
r'CoroWrapper.*foo.*was never')
|
|
||||||
|
|
||||||
|
self.assertEqual(sys.get_coroutine_origin_tracking_depth(), 0)
|
||||||
self.loop.set_debug(True)
|
self.loop.set_debug(True)
|
||||||
self.loop.run_until_complete(start())
|
self.loop.run_until_complete(start())
|
||||||
|
self.assertEqual(sys.get_coroutine_origin_tracking_depth(), 0)
|
||||||
async def start():
|
|
||||||
foo_coro = foo()
|
|
||||||
task = asyncio.ensure_future(foo_coro, loop=self.loop)
|
|
||||||
self.assertRegex(repr(task), r'Task.*foo.*running')
|
|
||||||
|
|
||||||
self.loop.run_until_complete(start())
|
|
||||||
|
|
||||||
|
|
||||||
def test_types_coroutine(self):
|
def test_types_coroutine(self):
|
||||||
def gen():
|
def gen():
|
||||||
|
@ -226,9 +206,9 @@ class CoroutineTests(BaseTest):
|
||||||
t.cancel()
|
t.cancel()
|
||||||
|
|
||||||
self.loop.set_debug(True)
|
self.loop.set_debug(True)
|
||||||
with self.assertRaisesRegex(
|
with self.assertRaises(
|
||||||
RuntimeError,
|
RuntimeError,
|
||||||
r'Cannot await.*test_double_await.*\bafunc\b.*while.*\bsleep\b'):
|
msg='coroutine is being awaited already'):
|
||||||
|
|
||||||
self.loop.run_until_complete(runner())
|
self.loop.run_until_complete(runner())
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import contextlib
|
||||||
import copy
|
import copy
|
||||||
import inspect
|
import inspect
|
||||||
import pickle
|
import pickle
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
import types
|
import types
|
||||||
import unittest
|
import unittest
|
||||||
|
@ -1974,8 +1975,10 @@ class SysSetCoroWrapperTest(unittest.TestCase):
|
||||||
wrapped = gen
|
wrapped = gen
|
||||||
return gen
|
return gen
|
||||||
|
|
||||||
|
with self.assertWarns(DeprecationWarning):
|
||||||
self.assertIsNone(sys.get_coroutine_wrapper())
|
self.assertIsNone(sys.get_coroutine_wrapper())
|
||||||
|
|
||||||
|
with self.assertWarns(DeprecationWarning):
|
||||||
sys.set_coroutine_wrapper(wrap)
|
sys.set_coroutine_wrapper(wrap)
|
||||||
self.assertIs(sys.get_coroutine_wrapper(), wrap)
|
self.assertIs(sys.get_coroutine_wrapper(), wrap)
|
||||||
try:
|
try:
|
||||||
|
@ -2041,6 +2044,130 @@ class SysSetCoroWrapperTest(unittest.TestCase):
|
||||||
sys.set_coroutine_wrapper(None)
|
sys.set_coroutine_wrapper(None)
|
||||||
|
|
||||||
|
|
||||||
|
class OriginTrackingTest(unittest.TestCase):
|
||||||
|
def here(self):
|
||||||
|
info = inspect.getframeinfo(inspect.currentframe().f_back)
|
||||||
|
return (info.filename, info.lineno)
|
||||||
|
|
||||||
|
def test_origin_tracking(self):
|
||||||
|
orig_depth = sys.get_coroutine_origin_tracking_depth()
|
||||||
|
try:
|
||||||
|
async def corofn():
|
||||||
|
pass
|
||||||
|
|
||||||
|
sys.set_coroutine_origin_tracking_depth(0)
|
||||||
|
self.assertEqual(sys.get_coroutine_origin_tracking_depth(), 0)
|
||||||
|
|
||||||
|
with contextlib.closing(corofn()) as coro:
|
||||||
|
self.assertIsNone(coro.cr_origin)
|
||||||
|
|
||||||
|
sys.set_coroutine_origin_tracking_depth(1)
|
||||||
|
self.assertEqual(sys.get_coroutine_origin_tracking_depth(), 1)
|
||||||
|
|
||||||
|
fname, lineno = self.here()
|
||||||
|
with contextlib.closing(corofn()) as coro:
|
||||||
|
self.assertEqual(coro.cr_origin,
|
||||||
|
((fname, lineno + 1, "test_origin_tracking"),))
|
||||||
|
|
||||||
|
sys.set_coroutine_origin_tracking_depth(2)
|
||||||
|
self.assertEqual(sys.get_coroutine_origin_tracking_depth(), 2)
|
||||||
|
|
||||||
|
def nested():
|
||||||
|
return (self.here(), corofn())
|
||||||
|
fname, lineno = self.here()
|
||||||
|
((nested_fname, nested_lineno), coro) = nested()
|
||||||
|
with contextlib.closing(coro):
|
||||||
|
self.assertEqual(coro.cr_origin,
|
||||||
|
((nested_fname, nested_lineno, "nested"),
|
||||||
|
(fname, lineno + 1, "test_origin_tracking")))
|
||||||
|
|
||||||
|
# Check we handle running out of frames correctly
|
||||||
|
sys.set_coroutine_origin_tracking_depth(1000)
|
||||||
|
with contextlib.closing(corofn()) as coro:
|
||||||
|
self.assertTrue(2 < len(coro.cr_origin) < 1000)
|
||||||
|
|
||||||
|
# We can't set depth negative
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
sys.set_coroutine_origin_tracking_depth(-1)
|
||||||
|
# And trying leaves it unchanged
|
||||||
|
self.assertEqual(sys.get_coroutine_origin_tracking_depth(), 1000)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
sys.set_coroutine_origin_tracking_depth(orig_depth)
|
||||||
|
|
||||||
|
def test_origin_tracking_warning(self):
|
||||||
|
async def corofn():
|
||||||
|
pass
|
||||||
|
|
||||||
|
a1_filename, a1_lineno = self.here()
|
||||||
|
def a1():
|
||||||
|
return corofn() # comment in a1
|
||||||
|
a1_lineno += 2
|
||||||
|
|
||||||
|
a2_filename, a2_lineno = self.here()
|
||||||
|
def a2():
|
||||||
|
return a1() # comment in a2
|
||||||
|
a2_lineno += 2
|
||||||
|
|
||||||
|
def check(depth, msg):
|
||||||
|
sys.set_coroutine_origin_tracking_depth(depth)
|
||||||
|
with warnings.catch_warnings(record=True) as wlist:
|
||||||
|
a2()
|
||||||
|
support.gc_collect()
|
||||||
|
# This might be fragile if other warnings somehow get triggered
|
||||||
|
# inside our 'with' block... let's worry about that if/when it
|
||||||
|
# happens.
|
||||||
|
self.assertTrue(len(wlist) == 1)
|
||||||
|
self.assertIs(wlist[0].category, RuntimeWarning)
|
||||||
|
self.assertEqual(msg, str(wlist[0].message))
|
||||||
|
|
||||||
|
orig_depth = sys.get_coroutine_origin_tracking_depth()
|
||||||
|
try:
|
||||||
|
msg = check(0, f"coroutine '{corofn.__qualname__}' was never awaited")
|
||||||
|
check(1, "".join([
|
||||||
|
f"coroutine '{corofn.__qualname__}' was never awaited\n",
|
||||||
|
"Coroutine created at (most recent call last)\n",
|
||||||
|
f' File "{a1_filename}", line {a1_lineno}, in a1\n',
|
||||||
|
f' return corofn() # comment in a1',
|
||||||
|
]))
|
||||||
|
check(2, "".join([
|
||||||
|
f"coroutine '{corofn.__qualname__}' was never awaited\n",
|
||||||
|
"Coroutine created at (most recent call last)\n",
|
||||||
|
f' File "{a2_filename}", line {a2_lineno}, in a2\n',
|
||||||
|
f' return a1() # comment in a2\n',
|
||||||
|
f' File "{a1_filename}", line {a1_lineno}, in a1\n',
|
||||||
|
f' return corofn() # comment in a1',
|
||||||
|
]))
|
||||||
|
|
||||||
|
finally:
|
||||||
|
sys.set_coroutine_origin_tracking_depth(orig_depth)
|
||||||
|
|
||||||
|
def test_unawaited_warning_when_module_broken(self):
|
||||||
|
# Make sure we don't blow up too bad if
|
||||||
|
# warnings._warn_unawaited_coroutine is broken somehow (e.g. because
|
||||||
|
# of shutdown problems)
|
||||||
|
async def corofn():
|
||||||
|
pass
|
||||||
|
|
||||||
|
orig_wuc = warnings._warn_unawaited_coroutine
|
||||||
|
try:
|
||||||
|
warnings._warn_unawaited_coroutine = lambda coro: 1/0
|
||||||
|
with support.captured_stderr() as stream:
|
||||||
|
corofn()
|
||||||
|
support.gc_collect()
|
||||||
|
self.assertIn("Exception ignored in", stream.getvalue())
|
||||||
|
self.assertIn("ZeroDivisionError", stream.getvalue())
|
||||||
|
self.assertIn("was never awaited", stream.getvalue())
|
||||||
|
|
||||||
|
del warnings._warn_unawaited_coroutine
|
||||||
|
with support.captured_stderr() as stream:
|
||||||
|
corofn()
|
||||||
|
support.gc_collect()
|
||||||
|
self.assertIn("was never awaited", stream.getvalue())
|
||||||
|
|
||||||
|
finally:
|
||||||
|
warnings._warn_unawaited_coroutine = orig_wuc
|
||||||
|
|
||||||
@support.cpython_only
|
@support.cpython_only
|
||||||
class CAPITest(unittest.TestCase):
|
class CAPITest(unittest.TestCase):
|
||||||
|
|
||||||
|
|
|
@ -488,6 +488,29 @@ class catch_warnings(object):
|
||||||
self._module._showwarnmsg_impl = self._showwarnmsg_impl
|
self._module._showwarnmsg_impl = self._showwarnmsg_impl
|
||||||
|
|
||||||
|
|
||||||
|
# Private utility function called by _PyErr_WarnUnawaitedCoroutine
|
||||||
|
def _warn_unawaited_coroutine(coro):
|
||||||
|
msg_lines = [
|
||||||
|
f"coroutine '{coro.__qualname__}' was never awaited\n"
|
||||||
|
]
|
||||||
|
if coro.cr_origin is not None:
|
||||||
|
import linecache, traceback
|
||||||
|
def extract():
|
||||||
|
for filename, lineno, funcname in reversed(coro.cr_origin):
|
||||||
|
line = linecache.getline(filename, lineno)
|
||||||
|
yield (filename, lineno, funcname, line)
|
||||||
|
msg_lines.append("Coroutine created at (most recent call last)\n")
|
||||||
|
msg_lines += traceback.format_list(list(extract()))
|
||||||
|
msg = "".join(msg_lines).rstrip("\n")
|
||||||
|
# Passing source= here means that if the user happens to have tracemalloc
|
||||||
|
# enabled and tracking where the coroutine was created, the warning will
|
||||||
|
# contain that traceback. This does mean that if they have *both*
|
||||||
|
# coroutine origin tracking *and* tracemalloc enabled, they'll get two
|
||||||
|
# partially-redundant tracebacks. If we wanted to be clever we could
|
||||||
|
# probably detect this case and avoid it, but for now we don't bother.
|
||||||
|
warn(msg, category=RuntimeWarning, stacklevel=2, source=coro)
|
||||||
|
|
||||||
|
|
||||||
# filters contains a sequence of filter 5-tuples
|
# filters contains a sequence of filter 5-tuples
|
||||||
# The components of the 5-tuple are:
|
# The components of the 5-tuple are:
|
||||||
# - an action: error, ignore, always, default, module, or once
|
# - an action: error, ignore, always, default, module, or once
|
||||||
|
|
|
@ -1486,6 +1486,7 @@ Christopher Smith
|
||||||
Eric V. Smith
|
Eric V. Smith
|
||||||
Gregory P. Smith
|
Gregory P. Smith
|
||||||
Mark Smith
|
Mark Smith
|
||||||
|
Nathaniel J. Smith
|
||||||
Roy Smith
|
Roy Smith
|
||||||
Ryan Smith-Roberts
|
Ryan Smith-Roberts
|
||||||
Rafal Smotrzyk
|
Rafal Smotrzyk
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Added built-in support for tracking the origin of coroutine objects; see
|
||||||
|
sys.set_coroutine_origin_tracking_depth and CoroutineType.cr_origin. This
|
||||||
|
replaces the asyncio debug mode's use of coroutine wrapping for native
|
||||||
|
coroutine objects.
|
|
@ -32,6 +32,8 @@ gen_traverse(PyGenObject *gen, visitproc visit, void *arg)
|
||||||
Py_VISIT(gen->gi_code);
|
Py_VISIT(gen->gi_code);
|
||||||
Py_VISIT(gen->gi_name);
|
Py_VISIT(gen->gi_name);
|
||||||
Py_VISIT(gen->gi_qualname);
|
Py_VISIT(gen->gi_qualname);
|
||||||
|
/* No need to visit cr_origin, because it's just tuples/str/int, so can't
|
||||||
|
participate in a reference cycle. */
|
||||||
return exc_state_traverse(&gen->gi_exc_state, visit, arg);
|
return exc_state_traverse(&gen->gi_exc_state, visit, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,9 +77,7 @@ _PyGen_Finalize(PyObject *self)
|
||||||
((PyCodeObject *)gen->gi_code)->co_flags & CO_COROUTINE &&
|
((PyCodeObject *)gen->gi_code)->co_flags & CO_COROUTINE &&
|
||||||
gen->gi_frame->f_lasti == -1) {
|
gen->gi_frame->f_lasti == -1) {
|
||||||
if (!error_value) {
|
if (!error_value) {
|
||||||
PyErr_WarnFormat(PyExc_RuntimeWarning, 1,
|
_PyErr_WarnUnawaitedCoroutine((PyObject *)gen);
|
||||||
"coroutine '%.50S' was never awaited",
|
|
||||||
gen->gi_qualname);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -137,6 +137,9 @@ gen_dealloc(PyGenObject *gen)
|
||||||
gen->gi_frame->f_gen = NULL;
|
gen->gi_frame->f_gen = NULL;
|
||||||
Py_CLEAR(gen->gi_frame);
|
Py_CLEAR(gen->gi_frame);
|
||||||
}
|
}
|
||||||
|
if (((PyCodeObject *)gen->gi_code)->co_flags & CO_COROUTINE) {
|
||||||
|
Py_CLEAR(((PyCoroObject *)gen)->cr_origin);
|
||||||
|
}
|
||||||
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);
|
||||||
|
@ -990,6 +993,7 @@ static PyMemberDef coro_memberlist[] = {
|
||||||
{"cr_frame", T_OBJECT, offsetof(PyCoroObject, cr_frame), READONLY},
|
{"cr_frame", T_OBJECT, offsetof(PyCoroObject, cr_frame), READONLY},
|
||||||
{"cr_running", T_BOOL, offsetof(PyCoroObject, cr_running), READONLY},
|
{"cr_running", T_BOOL, offsetof(PyCoroObject, cr_running), READONLY},
|
||||||
{"cr_code", T_OBJECT, offsetof(PyCoroObject, cr_code), READONLY},
|
{"cr_code", T_OBJECT, offsetof(PyCoroObject, cr_code), READONLY},
|
||||||
|
{"cr_origin", T_OBJECT, offsetof(PyCoroObject, cr_origin), READONLY},
|
||||||
{NULL} /* Sentinel */
|
{NULL} /* Sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1158,10 +1162,59 @@ PyTypeObject _PyCoroWrapper_Type = {
|
||||||
0, /* tp_free */
|
0, /* tp_free */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
compute_cr_origin(int origin_depth)
|
||||||
|
{
|
||||||
|
PyFrameObject *frame = PyEval_GetFrame();
|
||||||
|
/* First count how many frames we have */
|
||||||
|
int frame_count = 0;
|
||||||
|
for (; frame && frame_count < origin_depth; ++frame_count) {
|
||||||
|
frame = frame->f_back;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Now collect them */
|
||||||
|
PyObject *cr_origin = PyTuple_New(frame_count);
|
||||||
|
frame = PyEval_GetFrame();
|
||||||
|
for (int i = 0; i < frame_count; ++i) {
|
||||||
|
PyObject *frameinfo = Py_BuildValue(
|
||||||
|
"OiO",
|
||||||
|
frame->f_code->co_filename,
|
||||||
|
PyFrame_GetLineNumber(frame),
|
||||||
|
frame->f_code->co_name);
|
||||||
|
if (!frameinfo) {
|
||||||
|
Py_DECREF(cr_origin);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
PyTuple_SET_ITEM(cr_origin, i, frameinfo);
|
||||||
|
frame = frame->f_back;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cr_origin;
|
||||||
|
}
|
||||||
|
|
||||||
PyObject *
|
PyObject *
|
||||||
PyCoro_New(PyFrameObject *f, PyObject *name, PyObject *qualname)
|
PyCoro_New(PyFrameObject *f, PyObject *name, PyObject *qualname)
|
||||||
{
|
{
|
||||||
return gen_new_with_qualname(&PyCoro_Type, f, name, qualname);
|
PyObject *coro = gen_new_with_qualname(&PyCoro_Type, f, name, qualname);
|
||||||
|
if (!coro) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyThreadState *tstate = PyThreadState_GET();
|
||||||
|
int origin_depth = tstate->coroutine_origin_tracking_depth;
|
||||||
|
|
||||||
|
if (origin_depth == 0) {
|
||||||
|
((PyCoroObject *)coro)->cr_origin = NULL;
|
||||||
|
} else {
|
||||||
|
PyObject *cr_origin = compute_cr_origin(origin_depth);
|
||||||
|
if (!cr_origin) {
|
||||||
|
Py_DECREF(coro);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
((PyCoroObject *)coro)->cr_origin = cr_origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
return coro;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1153,6 +1153,53 @@ exit:
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
_PyErr_WarnUnawaitedCoroutine(PyObject *coro)
|
||||||
|
{
|
||||||
|
/* First, we attempt to funnel the warning through
|
||||||
|
warnings._warn_unawaited_coroutine.
|
||||||
|
|
||||||
|
This could raise an exception, due to:
|
||||||
|
- a bug
|
||||||
|
- some kind of shutdown-related brokenness
|
||||||
|
- succeeding, but with an "error" warning filter installed, so the
|
||||||
|
warning is converted into a RuntimeWarning exception
|
||||||
|
|
||||||
|
In the first two cases, we want to print the error (so we know what it
|
||||||
|
is!), and then print a warning directly as a fallback. In the last
|
||||||
|
case, we want to print the error (since it's the warning!), but *not*
|
||||||
|
do a fallback. And after we print the error we can't check for what
|
||||||
|
type of error it was (because PyErr_WriteUnraisable clears it), so we
|
||||||
|
need a flag to keep track.
|
||||||
|
|
||||||
|
Since this is called from __del__ context, it's careful to never raise
|
||||||
|
an exception.
|
||||||
|
*/
|
||||||
|
_Py_IDENTIFIER(_warn_unawaited_coroutine);
|
||||||
|
int warned = 0;
|
||||||
|
PyObject *fn = get_warnings_attr(&PyId__warn_unawaited_coroutine, 1);
|
||||||
|
if (fn) {
|
||||||
|
PyObject *res = PyObject_CallFunctionObjArgs(fn, coro, NULL);
|
||||||
|
Py_DECREF(fn);
|
||||||
|
if (res || PyErr_ExceptionMatches(PyExc_RuntimeWarning)) {
|
||||||
|
warned = 1;
|
||||||
|
}
|
||||||
|
Py_XDECREF(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PyErr_Occurred()) {
|
||||||
|
PyErr_WriteUnraisable(coro);
|
||||||
|
}
|
||||||
|
if (!warned) {
|
||||||
|
PyErr_WarnFormat(PyExc_RuntimeWarning, 1,
|
||||||
|
"coroutine '%.50S' was never awaited",
|
||||||
|
((PyCoroObject *)coro)->cr_qualname);
|
||||||
|
/* Maybe *that* got converted into an exception */
|
||||||
|
if (PyErr_Occurred()) {
|
||||||
|
PyErr_WriteUnraisable(coro);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PyDoc_STRVAR(warn_explicit_doc,
|
PyDoc_STRVAR(warn_explicit_doc,
|
||||||
"Low-level inferface to warnings functionality.");
|
"Low-level inferface to warnings functionality.");
|
||||||
|
|
|
@ -4387,6 +4387,21 @@ PyEval_SetTrace(Py_tracefunc func, PyObject *arg)
|
||||||
|| (tstate->c_profilefunc != NULL));
|
|| (tstate->c_profilefunc != NULL));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
_PyEval_SetCoroutineOriginTrackingDepth(int new_depth)
|
||||||
|
{
|
||||||
|
assert(new_depth >= 0);
|
||||||
|
PyThreadState *tstate = PyThreadState_GET();
|
||||||
|
tstate->coroutine_origin_tracking_depth = new_depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
_PyEval_GetCoroutineOriginTrackingDepth(void)
|
||||||
|
{
|
||||||
|
PyThreadState *tstate = PyThreadState_GET();
|
||||||
|
return tstate->coroutine_origin_tracking_depth;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
_PyEval_SetCoroutineWrapper(PyObject *wrapper)
|
_PyEval_SetCoroutineWrapper(PyObject *wrapper)
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*[clinic input]
|
||||||
|
preserve
|
||||||
|
[clinic start generated code]*/
|
||||||
|
|
||||||
|
PyDoc_STRVAR(sys_set_coroutine_origin_tracking_depth__doc__,
|
||||||
|
"set_coroutine_origin_tracking_depth($module, /, depth)\n"
|
||||||
|
"--\n"
|
||||||
|
"\n"
|
||||||
|
"Enable or disable origin tracking for coroutine objects in this thread.\n"
|
||||||
|
"\n"
|
||||||
|
"Coroutine objects will track \'depth\' frames of traceback information about\n"
|
||||||
|
"where they came from, available in their cr_origin attribute. Set depth of 0\n"
|
||||||
|
"to disable.");
|
||||||
|
|
||||||
|
#define SYS_SET_COROUTINE_ORIGIN_TRACKING_DEPTH_METHODDEF \
|
||||||
|
{"set_coroutine_origin_tracking_depth", (PyCFunction)sys_set_coroutine_origin_tracking_depth, METH_FASTCALL|METH_KEYWORDS, sys_set_coroutine_origin_tracking_depth__doc__},
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
sys_set_coroutine_origin_tracking_depth_impl(PyObject *module, int depth);
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
sys_set_coroutine_origin_tracking_depth(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
|
||||||
|
{
|
||||||
|
PyObject *return_value = NULL;
|
||||||
|
static const char * const _keywords[] = {"depth", NULL};
|
||||||
|
static _PyArg_Parser _parser = {"i:set_coroutine_origin_tracking_depth", _keywords, 0};
|
||||||
|
int depth;
|
||||||
|
|
||||||
|
if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser,
|
||||||
|
&depth)) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
return_value = sys_set_coroutine_origin_tracking_depth_impl(module, depth);
|
||||||
|
|
||||||
|
exit:
|
||||||
|
return return_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(sys_get_coroutine_origin_tracking_depth__doc__,
|
||||||
|
"get_coroutine_origin_tracking_depth($module, /)\n"
|
||||||
|
"--\n"
|
||||||
|
"\n"
|
||||||
|
"Check status of origin tracking for coroutine objects in this thread.");
|
||||||
|
|
||||||
|
#define SYS_GET_COROUTINE_ORIGIN_TRACKING_DEPTH_METHODDEF \
|
||||||
|
{"get_coroutine_origin_tracking_depth", (PyCFunction)sys_get_coroutine_origin_tracking_depth, METH_NOARGS, sys_get_coroutine_origin_tracking_depth__doc__},
|
||||||
|
|
||||||
|
static int
|
||||||
|
sys_get_coroutine_origin_tracking_depth_impl(PyObject *module);
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
sys_get_coroutine_origin_tracking_depth(PyObject *module, PyObject *Py_UNUSED(ignored))
|
||||||
|
{
|
||||||
|
PyObject *return_value = NULL;
|
||||||
|
int _return_value;
|
||||||
|
|
||||||
|
_return_value = sys_get_coroutine_origin_tracking_depth_impl(module);
|
||||||
|
if ((_return_value == -1) && PyErr_Occurred()) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
return_value = PyLong_FromLong((long)_return_value);
|
||||||
|
|
||||||
|
exit:
|
||||||
|
return return_value;
|
||||||
|
}
|
||||||
|
/*[clinic end generated code: output=4a3ac42b97d710ff input=a9049054013a1b77]*/
|
|
@ -305,6 +305,8 @@ new_threadstate(PyInterpreterState *interp, int init)
|
||||||
tstate->on_delete = NULL;
|
tstate->on_delete = NULL;
|
||||||
tstate->on_delete_data = NULL;
|
tstate->on_delete_data = NULL;
|
||||||
|
|
||||||
|
tstate->coroutine_origin_tracking_depth = 0;
|
||||||
|
|
||||||
tstate->coroutine_wrapper = NULL;
|
tstate->coroutine_wrapper = NULL;
|
||||||
tstate->in_coroutine_wrapper = 0;
|
tstate->in_coroutine_wrapper = 0;
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,13 @@ extern void *PyWin_DLLhModule;
|
||||||
extern const char *PyWin_DLLVersionString;
|
extern const char *PyWin_DLLVersionString;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/*[clinic input]
|
||||||
|
module sys
|
||||||
|
[clinic start generated code]*/
|
||||||
|
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=3726b388feee8cea]*/
|
||||||
|
|
||||||
|
#include "clinic/sysmodule.c.h"
|
||||||
|
|
||||||
_Py_IDENTIFIER(_);
|
_Py_IDENTIFIER(_);
|
||||||
_Py_IDENTIFIER(__sizeof__);
|
_Py_IDENTIFIER(__sizeof__);
|
||||||
_Py_IDENTIFIER(_xoptions);
|
_Py_IDENTIFIER(_xoptions);
|
||||||
|
@ -710,9 +717,51 @@ sys_setrecursionlimit(PyObject *self, PyObject *args)
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*[clinic input]
|
||||||
|
sys.set_coroutine_origin_tracking_depth
|
||||||
|
|
||||||
|
depth: int
|
||||||
|
|
||||||
|
Enable or disable origin tracking for coroutine objects in this thread.
|
||||||
|
|
||||||
|
Coroutine objects will track 'depth' frames of traceback information about
|
||||||
|
where they came from, available in their cr_origin attribute. Set depth of 0
|
||||||
|
to disable.
|
||||||
|
[clinic start generated code]*/
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
sys_set_coroutine_origin_tracking_depth_impl(PyObject *module, int depth)
|
||||||
|
/*[clinic end generated code: output=0a2123c1cc6759c5 input=9083112cccc1bdcb]*/
|
||||||
|
{
|
||||||
|
if (depth < 0) {
|
||||||
|
PyErr_SetString(PyExc_ValueError, "depth must be >= 0");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
_PyEval_SetCoroutineOriginTrackingDepth(depth);
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*[clinic input]
|
||||||
|
sys.get_coroutine_origin_tracking_depth -> int
|
||||||
|
|
||||||
|
Check status of origin tracking for coroutine objects in this thread.
|
||||||
|
[clinic start generated code]*/
|
||||||
|
|
||||||
|
static int
|
||||||
|
sys_get_coroutine_origin_tracking_depth_impl(PyObject *module)
|
||||||
|
/*[clinic end generated code: output=3699f7be95a3afb8 input=335266a71205b61a]*/
|
||||||
|
{
|
||||||
|
return _PyEval_GetCoroutineOriginTrackingDepth();
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
sys_set_coroutine_wrapper(PyObject *self, PyObject *wrapper)
|
sys_set_coroutine_wrapper(PyObject *self, PyObject *wrapper)
|
||||||
{
|
{
|
||||||
|
if (PyErr_WarnEx(PyExc_DeprecationWarning,
|
||||||
|
"set_coroutine_wrapper is deprecated", 1) < 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
if (wrapper != Py_None) {
|
if (wrapper != Py_None) {
|
||||||
if (!PyCallable_Check(wrapper)) {
|
if (!PyCallable_Check(wrapper)) {
|
||||||
PyErr_Format(PyExc_TypeError,
|
PyErr_Format(PyExc_TypeError,
|
||||||
|
@ -737,6 +786,10 @@ Set a wrapper for coroutine objects."
|
||||||
static PyObject *
|
static PyObject *
|
||||||
sys_get_coroutine_wrapper(PyObject *self, PyObject *args)
|
sys_get_coroutine_wrapper(PyObject *self, PyObject *args)
|
||||||
{
|
{
|
||||||
|
if (PyErr_WarnEx(PyExc_DeprecationWarning,
|
||||||
|
"get_coroutine_wrapper is deprecated", 1) < 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
PyObject *wrapper = _PyEval_GetCoroutineWrapper();
|
PyObject *wrapper = _PyEval_GetCoroutineWrapper();
|
||||||
if (wrapper == NULL) {
|
if (wrapper == NULL) {
|
||||||
wrapper = Py_None;
|
wrapper = Py_None;
|
||||||
|
@ -1512,6 +1565,8 @@ static PyMethodDef sys_methods[] = {
|
||||||
{"call_tracing", sys_call_tracing, METH_VARARGS, call_tracing_doc},
|
{"call_tracing", sys_call_tracing, METH_VARARGS, call_tracing_doc},
|
||||||
{"_debugmallocstats", sys_debugmallocstats, METH_NOARGS,
|
{"_debugmallocstats", sys_debugmallocstats, METH_NOARGS,
|
||||||
debugmallocstats_doc},
|
debugmallocstats_doc},
|
||||||
|
SYS_SET_COROUTINE_ORIGIN_TRACKING_DEPTH_METHODDEF
|
||||||
|
SYS_GET_COROUTINE_ORIGIN_TRACKING_DEPTH_METHODDEF
|
||||||
{"set_coroutine_wrapper", sys_set_coroutine_wrapper, METH_O,
|
{"set_coroutine_wrapper", sys_set_coroutine_wrapper, METH_O,
|
||||||
set_coroutine_wrapper_doc},
|
set_coroutine_wrapper_doc},
|
||||||
{"get_coroutine_wrapper", sys_get_coroutine_wrapper, METH_NOARGS,
|
{"get_coroutine_wrapper", sys_get_coroutine_wrapper, METH_NOARGS,
|
||||||
|
|
Loading…
Reference in New Issue