mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
bpo-43760: Add PyThreadState_EnterTracing() (GH-28542)
Add PyThreadState_EnterTracing() and PyThreadState_LeaveTracing() functions to the limited C API to suspend and resume tracing and profiling. Add an unit test on the PyThreadState C API to _testcapi. Add also internal _PyThreadState_DisableTracing() and _PyThreadState_ResetTracing().
This commit is contained in:
parent
354c35220d
commit
547d26aa08
9 changed files with 146 additions and 25 deletions
|
@ -1173,6 +1173,26 @@ All of the following functions must be called after :c:func:`Py_Initialize`.
|
||||||
.. versionadded:: 3.9
|
.. versionadded:: 3.9
|
||||||
|
|
||||||
|
|
||||||
|
.. c:function:: void PyThreadState_EnterTracing(PyThreadState *tstate)
|
||||||
|
|
||||||
|
Suspend tracing and profiling in the Python thread state *tstate*.
|
||||||
|
|
||||||
|
Resume them using the:c:func:`PyThreadState_LeaveTracing` function.
|
||||||
|
|
||||||
|
.. versionadded:: 3.11
|
||||||
|
|
||||||
|
|
||||||
|
.. c:function:: void PyThreadState_LeaveTracing(PyThreadState *tstate)
|
||||||
|
|
||||||
|
Resume tracing and profiling in the Python thread state *tstate* suspended
|
||||||
|
by the:c:func:`PyThreadState_EnterTracing` function.
|
||||||
|
|
||||||
|
See also :c:func:`PyEval_SetTrace` and :c:func:`PyEval_SetProfile`
|
||||||
|
functions.
|
||||||
|
|
||||||
|
.. versionadded:: 3.11
|
||||||
|
|
||||||
|
|
||||||
.. c:function:: PyInterpreterState* PyInterpreterState_Get(void)
|
.. c:function:: PyInterpreterState* PyInterpreterState_Get(void)
|
||||||
|
|
||||||
Get the current interpreter.
|
Get the current interpreter.
|
||||||
|
@ -1623,6 +1643,8 @@ Python-level trace functions in previous versions.
|
||||||
profile function is called for all monitored events except :const:`PyTrace_LINE`
|
profile function is called for all monitored events except :const:`PyTrace_LINE`
|
||||||
:const:`PyTrace_OPCODE` and :const:`PyTrace_EXCEPTION`.
|
:const:`PyTrace_OPCODE` and :const:`PyTrace_EXCEPTION`.
|
||||||
|
|
||||||
|
See also the :func:`sys.setprofile` function.
|
||||||
|
|
||||||
The caller must hold the :term:`GIL`.
|
The caller must hold the :term:`GIL`.
|
||||||
|
|
||||||
|
|
||||||
|
@ -1635,6 +1657,8 @@ Python-level trace functions in previous versions.
|
||||||
will not receive :const:`PyTrace_C_CALL`, :const:`PyTrace_C_EXCEPTION` or
|
will not receive :const:`PyTrace_C_CALL`, :const:`PyTrace_C_EXCEPTION` or
|
||||||
:const:`PyTrace_C_RETURN` as a value for the *what* parameter.
|
:const:`PyTrace_C_RETURN` as a value for the *what* parameter.
|
||||||
|
|
||||||
|
See also the :func:`sys.settrace` function.
|
||||||
|
|
||||||
The caller must hold the :term:`GIL`.
|
The caller must hold the :term:`GIL`.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -476,6 +476,11 @@ New Features
|
||||||
* Add a new :c:func:`PyType_GetQualName` function to get type's qualified name.
|
* Add a new :c:func:`PyType_GetQualName` function to get type's qualified name.
|
||||||
(Contributed by Hai Shi in :issue:`42035`.)
|
(Contributed by Hai Shi in :issue:`42035`.)
|
||||||
|
|
||||||
|
* Add new :c:func:`PyThreadState_EnterTracing` and
|
||||||
|
:c:func:`PyThreadState_LeaveTracing` functions to the limited C API to
|
||||||
|
suspend and resume tracing and profiling.
|
||||||
|
(Contributed by Victor Stinner in :issue:`43760`.)
|
||||||
|
|
||||||
Porting to Python 3.11
|
Porting to Python 3.11
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
|
|
@ -185,6 +185,13 @@ PyAPI_FUNC(PyThreadState *) _PyThreadState_UncheckedGet(void);
|
||||||
|
|
||||||
PyAPI_FUNC(PyObject *) _PyThreadState_GetDict(PyThreadState *tstate);
|
PyAPI_FUNC(PyObject *) _PyThreadState_GetDict(PyThreadState *tstate);
|
||||||
|
|
||||||
|
// Disable tracing and profiling.
|
||||||
|
PyAPI_FUNC(void) PyThreadState_EnterTracing(PyThreadState *tstate);
|
||||||
|
|
||||||
|
// Reset tracing and profiling: enable them if a trace function or a profile
|
||||||
|
// function is set, otherwise disable them.
|
||||||
|
PyAPI_FUNC(void) PyThreadState_LeaveTracing(PyThreadState *tstate);
|
||||||
|
|
||||||
/* PyGILState */
|
/* PyGILState */
|
||||||
|
|
||||||
/* Helper/diagnostic function - return 1 if the current thread
|
/* Helper/diagnostic function - return 1 if the current thread
|
||||||
|
|
|
@ -125,7 +125,7 @@ static inline PyInterpreterState* _PyInterpreterState_GET(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Other */
|
// PyThreadState functions
|
||||||
|
|
||||||
PyAPI_FUNC(void) _PyThreadState_Init(
|
PyAPI_FUNC(void) _PyThreadState_Init(
|
||||||
PyThreadState *tstate);
|
PyThreadState *tstate);
|
||||||
|
@ -133,6 +133,23 @@ PyAPI_FUNC(void) _PyThreadState_DeleteExcept(
|
||||||
_PyRuntimeState *runtime,
|
_PyRuntimeState *runtime,
|
||||||
PyThreadState *tstate);
|
PyThreadState *tstate);
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
_PyThreadState_DisableTracing(PyThreadState *tstate)
|
||||||
|
{
|
||||||
|
tstate->cframe->use_tracing = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
_PyThreadState_ResetTracing(PyThreadState *tstate)
|
||||||
|
{
|
||||||
|
int use_tracing = (tstate->c_tracefunc != NULL
|
||||||
|
|| tstate->c_profilefunc != NULL);
|
||||||
|
tstate->cframe->use_tracing = (use_tracing ? 255 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Other */
|
||||||
|
|
||||||
PyAPI_FUNC(PyThreadState *) _PyThreadState_Swap(
|
PyAPI_FUNC(PyThreadState *) _PyThreadState_Swap(
|
||||||
struct _gilstate_runtime_state *gilstate,
|
struct _gilstate_runtime_state *gilstate,
|
||||||
PyThreadState *newts);
|
PyThreadState *newts);
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Add new :c:func:`PyThreadState_EnterTracing`, and
|
||||||
|
:c:func:`PyThreadState_LeaveTracing` functions to the limited C API to suspend
|
||||||
|
and resume tracing and profiling.
|
||||||
|
Patch by Victor Stinner.
|
|
@ -18,29 +18,32 @@
|
||||||
#define PY_SSIZE_T_CLEAN
|
#define PY_SSIZE_T_CLEAN
|
||||||
|
|
||||||
#include "Python.h"
|
#include "Python.h"
|
||||||
#include "datetime.h"
|
#include "frameobject.h" // PyFrame_Check()
|
||||||
#include "marshal.h"
|
#include "datetime.h" // PyDateTimeAPI
|
||||||
|
#include "marshal.h" // PyMarshal_WriteLongToFile
|
||||||
#include "structmember.h" // PyMemberDef
|
#include "structmember.h" // PyMemberDef
|
||||||
#include <float.h>
|
#include <float.h> // FLT_MAX
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
|
||||||
#ifdef MS_WINDOWS
|
#ifdef MS_WINDOWS
|
||||||
# include <winsock2.h> /* struct timeval */
|
# include <winsock2.h> // struct timeval
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_SYS_WAIT_H
|
#ifdef HAVE_SYS_WAIT_H
|
||||||
#include <sys/wait.h> /* For W_STOPCODE */
|
#include <sys/wait.h> // W_STOPCODE
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef Py_BUILD_CORE
|
#ifdef Py_BUILD_CORE
|
||||||
# error "_testcapi must test the public Python C API, not CPython internal C API"
|
# error "_testcapi must test the public Python C API, not CPython internal C API"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
// Forward declarations
|
||||||
static struct PyModuleDef _testcapimodule;
|
static struct PyModuleDef _testcapimodule;
|
||||||
static PyType_Spec HeapTypeNameType_Spec;
|
static PyType_Spec HeapTypeNameType_Spec;
|
||||||
|
|
||||||
static PyObject *TestError; /* set to exception object in init */
|
static PyObject *TestError; /* set to exception object in init */
|
||||||
|
|
||||||
|
|
||||||
/* Raise TestError with test_name + ": " + msg, and return NULL. */
|
/* Raise TestError with test_name + ": " + msg, and return NULL. */
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
@ -5674,6 +5677,57 @@ type_get_version(PyObject *self, PyObject *type)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Test PyThreadState C API
|
||||||
|
static PyObject *
|
||||||
|
test_tstate_capi(PyObject *self, PyObject *Py_UNUSED(args))
|
||||||
|
{
|
||||||
|
// PyThreadState_Get()
|
||||||
|
PyThreadState *tstate = PyThreadState_Get();
|
||||||
|
assert(tstate != NULL);
|
||||||
|
|
||||||
|
// PyThreadState_GET()
|
||||||
|
PyThreadState *tstate2 = PyThreadState_Get();
|
||||||
|
assert(tstate2 == tstate);
|
||||||
|
|
||||||
|
// private _PyThreadState_UncheckedGet()
|
||||||
|
PyThreadState *tstate3 = _PyThreadState_UncheckedGet();
|
||||||
|
assert(tstate3 == tstate);
|
||||||
|
|
||||||
|
// PyThreadState_EnterTracing(), PyThreadState_LeaveTracing()
|
||||||
|
PyThreadState_EnterTracing(tstate);
|
||||||
|
PyThreadState_LeaveTracing(tstate);
|
||||||
|
|
||||||
|
// PyThreadState_GetDict(): no tstate argument
|
||||||
|
PyObject *dict = PyThreadState_GetDict();
|
||||||
|
// PyThreadState_GetDict() API can return NULL if PyDict_New() fails,
|
||||||
|
// but it should not occur in practice.
|
||||||
|
assert(dict != NULL);
|
||||||
|
assert(PyDict_Check(dict));
|
||||||
|
// dict is a borrowed reference
|
||||||
|
|
||||||
|
// private _PyThreadState_GetDict()
|
||||||
|
PyObject *dict2 = _PyThreadState_GetDict(tstate);
|
||||||
|
assert(dict2 == dict);
|
||||||
|
// dict2 is a borrowed reference
|
||||||
|
|
||||||
|
// PyThreadState_GetInterpreter()
|
||||||
|
PyInterpreterState *interp = PyThreadState_GetInterpreter(tstate);
|
||||||
|
assert(interp != NULL);
|
||||||
|
|
||||||
|
// PyThreadState_GetFrame()
|
||||||
|
PyFrameObject*frame = PyThreadState_GetFrame(tstate);
|
||||||
|
assert(frame != NULL);
|
||||||
|
assert(PyFrame_Check(frame));
|
||||||
|
Py_DECREF(frame);
|
||||||
|
|
||||||
|
// PyThreadState_GetID()
|
||||||
|
uint64_t id = PyThreadState_GetID(tstate);
|
||||||
|
assert(id >= 1);
|
||||||
|
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static PyObject *test_buildvalue_issue38913(PyObject *, PyObject *);
|
static PyObject *test_buildvalue_issue38913(PyObject *, PyObject *);
|
||||||
static PyObject *getargs_s_hash_int(PyObject *, PyObject *, PyObject*);
|
static PyObject *getargs_s_hash_int(PyObject *, PyObject *, PyObject*);
|
||||||
|
|
||||||
|
@ -5957,6 +6011,7 @@ static PyMethodDef TestMethods[] = {
|
||||||
{"fatal_error", test_fatal_error, METH_VARARGS,
|
{"fatal_error", test_fatal_error, METH_VARARGS,
|
||||||
PyDoc_STR("fatal_error(message, release_gil=False): call Py_FatalError(message)")},
|
PyDoc_STR("fatal_error(message, release_gil=False): call Py_FatalError(message)")},
|
||||||
{"type_get_version", type_get_version, METH_O, PyDoc_STR("type->tp_version_tag")},
|
{"type_get_version", type_get_version, METH_O, PyDoc_STR("type->tp_version_tag")},
|
||||||
|
{"test_tstate_capi", test_tstate_capi, METH_NOARGS, NULL},
|
||||||
{NULL, NULL} /* sentinel */
|
{NULL, NULL} /* sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6187,7 +6187,7 @@ call_trace(Py_tracefunc func, PyObject *obj,
|
||||||
if (tstate->tracing)
|
if (tstate->tracing)
|
||||||
return 0;
|
return 0;
|
||||||
tstate->tracing++;
|
tstate->tracing++;
|
||||||
tstate->cframe->use_tracing = 0;
|
_PyThreadState_DisableTracing(tstate);
|
||||||
PyFrameObject *f = _PyFrame_GetFrameObject(frame);
|
PyFrameObject *f = _PyFrame_GetFrameObject(frame);
|
||||||
if (f == NULL) {
|
if (f == NULL) {
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -6201,8 +6201,7 @@ call_trace(Py_tracefunc func, PyObject *obj,
|
||||||
}
|
}
|
||||||
result = func(obj, f, what, arg);
|
result = func(obj, f, what, arg);
|
||||||
f->f_lineno = 0;
|
f->f_lineno = 0;
|
||||||
tstate->cframe->use_tracing = ((tstate->c_tracefunc != NULL)
|
_PyThreadState_ResetTracing(tstate);
|
||||||
|| (tstate->c_profilefunc != NULL)) ? 255 : 0;
|
|
||||||
tstate->tracing--;
|
tstate->tracing--;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -6216,8 +6215,7 @@ _PyEval_CallTracing(PyObject *func, PyObject *args)
|
||||||
PyObject *result;
|
PyObject *result;
|
||||||
|
|
||||||
tstate->tracing = 0;
|
tstate->tracing = 0;
|
||||||
tstate->cframe->use_tracing = ((tstate->c_tracefunc != NULL)
|
_PyThreadState_ResetTracing(tstate);
|
||||||
|| (tstate->c_profilefunc != NULL)) ? 255 : 0;
|
|
||||||
result = PyObject_Call(func, args, NULL);
|
result = PyObject_Call(func, args, NULL);
|
||||||
tstate->tracing = save_tracing;
|
tstate->tracing = save_tracing;
|
||||||
tstate->cframe->use_tracing = save_use_tracing;
|
tstate->cframe->use_tracing = save_use_tracing;
|
||||||
|
@ -6274,7 +6272,7 @@ _PyEval_SetProfile(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
|
||||||
tstate->c_profilefunc = NULL;
|
tstate->c_profilefunc = NULL;
|
||||||
tstate->c_profileobj = NULL;
|
tstate->c_profileobj = NULL;
|
||||||
/* Must make sure that tracing is not ignored if 'profileobj' is freed */
|
/* Must make sure that tracing is not ignored if 'profileobj' is freed */
|
||||||
tstate->cframe->use_tracing = tstate->c_tracefunc != NULL;
|
_PyThreadState_ResetTracing(tstate);
|
||||||
Py_XDECREF(profileobj);
|
Py_XDECREF(profileobj);
|
||||||
|
|
||||||
Py_XINCREF(arg);
|
Py_XINCREF(arg);
|
||||||
|
@ -6282,7 +6280,7 @@ _PyEval_SetProfile(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
|
||||||
tstate->c_profilefunc = func;
|
tstate->c_profilefunc = func;
|
||||||
|
|
||||||
/* Flag that tracing or profiling is turned on */
|
/* Flag that tracing or profiling is turned on */
|
||||||
tstate->cframe->use_tracing = (func != NULL) || (tstate->c_tracefunc != NULL) ? 255 : 0;
|
_PyThreadState_ResetTracing(tstate);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6315,7 +6313,7 @@ _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
|
||||||
tstate->c_tracefunc = NULL;
|
tstate->c_tracefunc = NULL;
|
||||||
tstate->c_traceobj = NULL;
|
tstate->c_traceobj = NULL;
|
||||||
/* Must make sure that profiling is not ignored if 'traceobj' is freed */
|
/* Must make sure that profiling is not ignored if 'traceobj' is freed */
|
||||||
tstate->cframe->use_tracing = (tstate->c_profilefunc != NULL) ? 255 : 0;
|
_PyThreadState_ResetTracing(tstate);
|
||||||
Py_XDECREF(traceobj);
|
Py_XDECREF(traceobj);
|
||||||
|
|
||||||
Py_XINCREF(arg);
|
Py_XINCREF(arg);
|
||||||
|
@ -6323,8 +6321,7 @@ _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
|
||||||
tstate->c_tracefunc = func;
|
tstate->c_tracefunc = func;
|
||||||
|
|
||||||
/* Flag that tracing or profiling is turned on */
|
/* Flag that tracing or profiling is turned on */
|
||||||
tstate->cframe->use_tracing = ((func != NULL)
|
_PyThreadState_ResetTracing(tstate);
|
||||||
|| (tstate->c_profilefunc != NULL)) ? 255 : 0;
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1201,6 +1201,22 @@ PyThreadState_SetAsyncExc(unsigned long id, PyObject *exc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
PyThreadState_EnterTracing(PyThreadState *tstate)
|
||||||
|
{
|
||||||
|
tstate->tracing++;
|
||||||
|
_PyThreadState_DisableTracing(tstate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PyThreadState_LeaveTracing(PyThreadState *tstate)
|
||||||
|
{
|
||||||
|
tstate->tracing--;
|
||||||
|
_PyThreadState_ResetTracing(tstate);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Routines for advanced debuggers, requested by David Beazley.
|
/* Routines for advanced debuggers, requested by David Beazley.
|
||||||
Don't use unless you know what you are doing! */
|
Don't use unless you know what you are doing! */
|
||||||
|
|
||||||
|
|
|
@ -256,8 +256,7 @@ sys_audit_tstate(PyThreadState *ts, const char *event,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Disallow tracing in hooks unless explicitly enabled */
|
/* Disallow tracing in hooks unless explicitly enabled */
|
||||||
ts->tracing++;
|
PyThreadState_EnterTracing(ts);
|
||||||
ts->cframe->use_tracing = 0;
|
|
||||||
while ((hook = PyIter_Next(hooks)) != NULL) {
|
while ((hook = PyIter_Next(hooks)) != NULL) {
|
||||||
_Py_IDENTIFIER(__cantrace__);
|
_Py_IDENTIFIER(__cantrace__);
|
||||||
PyObject *o;
|
PyObject *o;
|
||||||
|
@ -270,14 +269,12 @@ sys_audit_tstate(PyThreadState *ts, const char *event,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (canTrace) {
|
if (canTrace) {
|
||||||
ts->cframe->use_tracing = (ts->c_tracefunc || ts->c_profilefunc) ? 255 : 0;
|
PyThreadState_LeaveTracing(ts);
|
||||||
ts->tracing--;
|
|
||||||
}
|
}
|
||||||
PyObject* args[2] = {eventName, eventArgs};
|
PyObject* args[2] = {eventName, eventArgs};
|
||||||
o = _PyObject_FastCallTstate(ts, hook, args, 2);
|
o = _PyObject_FastCallTstate(ts, hook, args, 2);
|
||||||
if (canTrace) {
|
if (canTrace) {
|
||||||
ts->tracing++;
|
PyThreadState_EnterTracing(ts);
|
||||||
ts->cframe->use_tracing = 0;
|
|
||||||
}
|
}
|
||||||
if (!o) {
|
if (!o) {
|
||||||
break;
|
break;
|
||||||
|
@ -285,8 +282,7 @@ sys_audit_tstate(PyThreadState *ts, const char *event,
|
||||||
Py_DECREF(o);
|
Py_DECREF(o);
|
||||||
Py_CLEAR(hook);
|
Py_CLEAR(hook);
|
||||||
}
|
}
|
||||||
ts->cframe->use_tracing = (ts->c_tracefunc || ts->c_profilefunc) ? 255 : 0;
|
PyThreadState_LeaveTracing(ts);
|
||||||
ts->tracing--;
|
|
||||||
if (_PyErr_Occurred(ts)) {
|
if (_PyErr_Occurred(ts)) {
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue