diff options
author | Victor Stinner <vstinner@python.org> | 2021-10-15 14:06:30 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-10-15 14:06:30 (GMT) |
commit | 547d26aa08aa5e4ec6e4f8a5587b30b39064a5ba (patch) | |
tree | f89a1327f1847ca97929efec97a12cab09478b2a | |
parent | 354c35220d25a893e502014478f6739dad6897f3 (diff) | |
download | cpython-547d26aa08aa5e4ec6e4f8a5587b30b39064a5ba.zip cpython-547d26aa08aa5e4ec6e4f8a5587b30b39064a5ba.tar.gz cpython-547d26aa08aa5e4ec6e4f8a5587b30b39064a5ba.tar.bz2 |
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().
-rw-r--r-- | Doc/c-api/init.rst | 24 | ||||
-rw-r--r-- | Doc/whatsnew/3.11.rst | 5 | ||||
-rw-r--r-- | Include/cpython/pystate.h | 7 | ||||
-rw-r--r-- | Include/internal/pycore_pystate.h | 19 | ||||
-rw-r--r-- | Misc/NEWS.d/next/C API/2021-09-24-11-12-21.bpo-43760.Bfxd1-.rst | 4 | ||||
-rw-r--r-- | Modules/_testcapimodule.c | 67 | ||||
-rw-r--r-- | Python/ceval.c | 17 | ||||
-rw-r--r-- | Python/pystate.c | 16 | ||||
-rw-r--r-- | Python/sysmodule.c | 12 |
9 files changed, 146 insertions, 25 deletions
diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index 7703672..5e81726 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -1173,6 +1173,26 @@ All of the following functions must be called after :c:func:`Py_Initialize`. .. 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) 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` :const:`PyTrace_OPCODE` and :const:`PyTrace_EXCEPTION`. + See also the :func:`sys.setprofile` function. + 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 :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`. diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index 8a3deaa..4704be5 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -476,6 +476,11 @@ New Features * Add a new :c:func:`PyType_GetQualName` function to get type's qualified name. (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 ---------------------- diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index ca0de87..528d2a2 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -185,6 +185,13 @@ PyAPI_FUNC(PyThreadState *) _PyThreadState_UncheckedGet(void); 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 */ /* Helper/diagnostic function - return 1 if the current thread diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h index 1b74e5f..d632397 100644 --- a/Include/internal/pycore_pystate.h +++ b/Include/internal/pycore_pystate.h @@ -125,7 +125,7 @@ static inline PyInterpreterState* _PyInterpreterState_GET(void) { } -/* Other */ +// PyThreadState functions PyAPI_FUNC(void) _PyThreadState_Init( PyThreadState *tstate); @@ -133,6 +133,23 @@ PyAPI_FUNC(void) _PyThreadState_DeleteExcept( _PyRuntimeState *runtime, 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( struct _gilstate_runtime_state *gilstate, PyThreadState *newts); diff --git a/Misc/NEWS.d/next/C API/2021-09-24-11-12-21.bpo-43760.Bfxd1-.rst b/Misc/NEWS.d/next/C API/2021-09-24-11-12-21.bpo-43760.Bfxd1-.rst new file mode 100644 index 0000000..16bea5f --- /dev/null +++ b/Misc/NEWS.d/next/C API/2021-09-24-11-12-21.bpo-43760.Bfxd1-.rst @@ -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. diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 82cfc04..6857241 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -18,29 +18,32 @@ #define PY_SSIZE_T_CLEAN #include "Python.h" -#include "datetime.h" -#include "marshal.h" +#include "frameobject.h" // PyFrame_Check() +#include "datetime.h" // PyDateTimeAPI +#include "marshal.h" // PyMarshal_WriteLongToFile #include "structmember.h" // PyMemberDef -#include <float.h> +#include <float.h> // FLT_MAX #include <signal.h> #ifdef MS_WINDOWS -# include <winsock2.h> /* struct timeval */ +# include <winsock2.h> // struct timeval #endif #ifdef HAVE_SYS_WAIT_H -#include <sys/wait.h> /* For W_STOPCODE */ +#include <sys/wait.h> // W_STOPCODE #endif #ifdef Py_BUILD_CORE # error "_testcapi must test the public Python C API, not CPython internal C API" #endif + +// Forward declarations static struct PyModuleDef _testcapimodule; static PyType_Spec HeapTypeNameType_Spec; - static PyObject *TestError; /* set to exception object in init */ + /* Raise TestError with test_name + ": " + msg, and return NULL. */ 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 *getargs_s_hash_int(PyObject *, PyObject *, PyObject*); @@ -5957,6 +6011,7 @@ static PyMethodDef TestMethods[] = { {"fatal_error", test_fatal_error, METH_VARARGS, 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")}, + {"test_tstate_capi", test_tstate_capi, METH_NOARGS, NULL}, {NULL, NULL} /* sentinel */ }; diff --git a/Python/ceval.c b/Python/ceval.c index de71ae5..8986872 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -6187,7 +6187,7 @@ call_trace(Py_tracefunc func, PyObject *obj, if (tstate->tracing) return 0; tstate->tracing++; - tstate->cframe->use_tracing = 0; + _PyThreadState_DisableTracing(tstate); PyFrameObject *f = _PyFrame_GetFrameObject(frame); if (f == NULL) { return -1; @@ -6201,8 +6201,7 @@ call_trace(Py_tracefunc func, PyObject *obj, } result = func(obj, f, what, arg); f->f_lineno = 0; - tstate->cframe->use_tracing = ((tstate->c_tracefunc != NULL) - || (tstate->c_profilefunc != NULL)) ? 255 : 0; + _PyThreadState_ResetTracing(tstate); tstate->tracing--; return result; } @@ -6216,8 +6215,7 @@ _PyEval_CallTracing(PyObject *func, PyObject *args) PyObject *result; tstate->tracing = 0; - tstate->cframe->use_tracing = ((tstate->c_tracefunc != NULL) - || (tstate->c_profilefunc != NULL)) ? 255 : 0; + _PyThreadState_ResetTracing(tstate); result = PyObject_Call(func, args, NULL); tstate->tracing = save_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_profileobj = NULL; /* 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_XINCREF(arg); @@ -6282,7 +6280,7 @@ _PyEval_SetProfile(PyThreadState *tstate, Py_tracefunc func, PyObject *arg) tstate->c_profilefunc = func; /* 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; } @@ -6315,7 +6313,7 @@ _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg) tstate->c_tracefunc = NULL; tstate->c_traceobj = NULL; /* 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_XINCREF(arg); @@ -6323,8 +6321,7 @@ _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg) tstate->c_tracefunc = func; /* Flag that tracing or profiling is turned on */ - tstate->cframe->use_tracing = ((func != NULL) - || (tstate->c_profilefunc != NULL)) ? 255 : 0; + _PyThreadState_ResetTracing(tstate); return 0; } diff --git a/Python/pystate.c b/Python/pystate.c index ee9507c..abd711e 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -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. Don't use unless you know what you are doing! */ diff --git a/Python/sysmodule.c b/Python/sysmodule.c index ad9be71..5e663c1 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -256,8 +256,7 @@ sys_audit_tstate(PyThreadState *ts, const char *event, } /* Disallow tracing in hooks unless explicitly enabled */ - ts->tracing++; - ts->cframe->use_tracing = 0; + PyThreadState_EnterTracing(ts); while ((hook = PyIter_Next(hooks)) != NULL) { _Py_IDENTIFIER(__cantrace__); PyObject *o; @@ -270,14 +269,12 @@ sys_audit_tstate(PyThreadState *ts, const char *event, break; } if (canTrace) { - ts->cframe->use_tracing = (ts->c_tracefunc || ts->c_profilefunc) ? 255 : 0; - ts->tracing--; + PyThreadState_LeaveTracing(ts); } PyObject* args[2] = {eventName, eventArgs}; o = _PyObject_FastCallTstate(ts, hook, args, 2); if (canTrace) { - ts->tracing++; - ts->cframe->use_tracing = 0; + PyThreadState_EnterTracing(ts); } if (!o) { break; @@ -285,8 +282,7 @@ sys_audit_tstate(PyThreadState *ts, const char *event, Py_DECREF(o); Py_CLEAR(hook); } - ts->cframe->use_tracing = (ts->c_tracefunc || ts->c_profilefunc) ? 255 : 0; - ts->tracing--; + PyThreadState_LeaveTracing(ts); if (_PyErr_Occurred(ts)) { goto exit; } |