Skip to content

Commit

Permalink
bpo-43760: Add PyThreadState_EnterTracing() (pythonGH-28542)
Browse files Browse the repository at this point in the history
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().
  • Loading branch information
vstinner authored Oct 15, 2021
1 parent 354c352 commit 547d26a
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 25 deletions.
24 changes: 24 additions & 0 deletions Doc/c-api/init.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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`.
Expand All @@ -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`.
Expand Down
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.11.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
----------------------

Expand Down
7 changes: 7 additions & 0 deletions Include/cpython/pystate.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 18 additions & 1 deletion Include/internal/pycore_pystate.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,14 +125,31 @@ static inline PyInterpreterState* _PyInterpreterState_GET(void) {
}


/* Other */
// PyThreadState functions

PyAPI_FUNC(void) _PyThreadState_Init(
PyThreadState *tstate);
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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
67 changes: 61 additions & 6 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 *
Expand Down Expand Up @@ -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*);

Expand Down Expand Up @@ -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 */
};

Expand Down
17 changes: 7 additions & 10 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}
Expand All @@ -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;
Expand Down Expand Up @@ -6274,15 +6272,15 @@ _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);
tstate->c_profileobj = 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;
}

Expand Down Expand Up @@ -6315,16 +6313,15 @@ _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);
tstate->c_traceobj = 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;
}
Expand Down
16 changes: 16 additions & 0 deletions Python/pystate.c
Original file line number Diff line number Diff line change
Expand Up @@ -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! */

Expand Down
12 changes: 4 additions & 8 deletions Python/sysmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -270,23 +269,20 @@ 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;
}
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;
}
Expand Down

0 comments on commit 547d26a

Please sign in to comment.