Skip to content

Commit 547d26a

Browse files
authored
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().
1 parent 354c352 commit 547d26a

File tree

9 files changed

+146
-25
lines changed

9 files changed

+146
-25
lines changed

Doc/c-api/init.rst

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1173,6 +1173,26 @@ All of the following functions must be called after :c:func:`Py_Initialize`.
11731173
.. versionadded:: 3.9
11741174
11751175
1176+
.. c:function:: void PyThreadState_EnterTracing(PyThreadState *tstate)
1177+
1178+
Suspend tracing and profiling in the Python thread state *tstate*.
1179+
1180+
Resume them using the:c:func:`PyThreadState_LeaveTracing` function.
1181+
1182+
.. versionadded:: 3.11
1183+
1184+
1185+
.. c:function:: void PyThreadState_LeaveTracing(PyThreadState *tstate)
1186+
1187+
Resume tracing and profiling in the Python thread state *tstate* suspended
1188+
by the:c:func:`PyThreadState_EnterTracing` function.
1189+
1190+
See also :c:func:`PyEval_SetTrace` and :c:func:`PyEval_SetProfile`
1191+
functions.
1192+
1193+
.. versionadded:: 3.11
1194+
1195+
11761196
.. c:function:: PyInterpreterState* PyInterpreterState_Get(void)
11771197
11781198
Get the current interpreter.
@@ -1623,6 +1643,8 @@ Python-level trace functions in previous versions.
16231643
profile function is called for all monitored events except :const:`PyTrace_LINE`
16241644
:const:`PyTrace_OPCODE` and :const:`PyTrace_EXCEPTION`.
16251645
1646+
See also the :func:`sys.setprofile` function.
1647+
16261648
The caller must hold the :term:`GIL`.
16271649
16281650
@@ -1635,6 +1657,8 @@ Python-level trace functions in previous versions.
16351657
will not receive :const:`PyTrace_C_CALL`, :const:`PyTrace_C_EXCEPTION` or
16361658
:const:`PyTrace_C_RETURN` as a value for the *what* parameter.
16371659
1660+
See also the :func:`sys.settrace` function.
1661+
16381662
The caller must hold the :term:`GIL`.
16391663
16401664

Doc/whatsnew/3.11.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,11 @@ New Features
476476
* Add a new :c:func:`PyType_GetQualName` function to get type's qualified name.
477477
(Contributed by Hai Shi in :issue:`42035`.)
478478

479+
* Add new :c:func:`PyThreadState_EnterTracing` and
480+
:c:func:`PyThreadState_LeaveTracing` functions to the limited C API to
481+
suspend and resume tracing and profiling.
482+
(Contributed by Victor Stinner in :issue:`43760`.)
483+
479484
Porting to Python 3.11
480485
----------------------
481486

Include/cpython/pystate.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,13 @@ PyAPI_FUNC(PyThreadState *) _PyThreadState_UncheckedGet(void);
185185

186186
PyAPI_FUNC(PyObject *) _PyThreadState_GetDict(PyThreadState *tstate);
187187

188+
// Disable tracing and profiling.
189+
PyAPI_FUNC(void) PyThreadState_EnterTracing(PyThreadState *tstate);
190+
191+
// Reset tracing and profiling: enable them if a trace function or a profile
192+
// function is set, otherwise disable them.
193+
PyAPI_FUNC(void) PyThreadState_LeaveTracing(PyThreadState *tstate);
194+
188195
/* PyGILState */
189196

190197
/* Helper/diagnostic function - return 1 if the current thread

Include/internal/pycore_pystate.h

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,14 +125,31 @@ static inline PyInterpreterState* _PyInterpreterState_GET(void) {
125125
}
126126

127127

128-
/* Other */
128+
// PyThreadState functions
129129

130130
PyAPI_FUNC(void) _PyThreadState_Init(
131131
PyThreadState *tstate);
132132
PyAPI_FUNC(void) _PyThreadState_DeleteExcept(
133133
_PyRuntimeState *runtime,
134134
PyThreadState *tstate);
135135

136+
static inline void
137+
_PyThreadState_DisableTracing(PyThreadState *tstate)
138+
{
139+
tstate->cframe->use_tracing = 0;
140+
}
141+
142+
static inline void
143+
_PyThreadState_ResetTracing(PyThreadState *tstate)
144+
{
145+
int use_tracing = (tstate->c_tracefunc != NULL
146+
|| tstate->c_profilefunc != NULL);
147+
tstate->cframe->use_tracing = (use_tracing ? 255 : 0);
148+
}
149+
150+
151+
/* Other */
152+
136153
PyAPI_FUNC(PyThreadState *) _PyThreadState_Swap(
137154
struct _gilstate_runtime_state *gilstate,
138155
PyThreadState *newts);
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Add new :c:func:`PyThreadState_EnterTracing`, and
2+
:c:func:`PyThreadState_LeaveTracing` functions to the limited C API to suspend
3+
and resume tracing and profiling.
4+
Patch by Victor Stinner.

Modules/_testcapimodule.c

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,29 +18,32 @@
1818
#define PY_SSIZE_T_CLEAN
1919

2020
#include "Python.h"
21-
#include "datetime.h"
22-
#include "marshal.h"
21+
#include "frameobject.h" // PyFrame_Check()
22+
#include "datetime.h" // PyDateTimeAPI
23+
#include "marshal.h" // PyMarshal_WriteLongToFile
2324
#include "structmember.h" // PyMemberDef
24-
#include <float.h>
25+
#include <float.h> // FLT_MAX
2526
#include <signal.h>
2627

2728
#ifdef MS_WINDOWS
28-
# include <winsock2.h> /* struct timeval */
29+
# include <winsock2.h> // struct timeval
2930
#endif
3031

3132
#ifdef HAVE_SYS_WAIT_H
32-
#include <sys/wait.h> /* For W_STOPCODE */
33+
#include <sys/wait.h> // W_STOPCODE
3334
#endif
3435

3536
#ifdef Py_BUILD_CORE
3637
# error "_testcapi must test the public Python C API, not CPython internal C API"
3738
#endif
3839

40+
41+
// Forward declarations
3942
static struct PyModuleDef _testcapimodule;
4043
static PyType_Spec HeapTypeNameType_Spec;
41-
4244
static PyObject *TestError; /* set to exception object in init */
4345

46+
4447
/* Raise TestError with test_name + ": " + msg, and return NULL. */
4548

4649
static PyObject *
@@ -5674,6 +5677,57 @@ type_get_version(PyObject *self, PyObject *type)
56745677
}
56755678

56765679

5680+
// Test PyThreadState C API
5681+
static PyObject *
5682+
test_tstate_capi(PyObject *self, PyObject *Py_UNUSED(args))
5683+
{
5684+
// PyThreadState_Get()
5685+
PyThreadState *tstate = PyThreadState_Get();
5686+
assert(tstate != NULL);
5687+
5688+
// PyThreadState_GET()
5689+
PyThreadState *tstate2 = PyThreadState_Get();
5690+
assert(tstate2 == tstate);
5691+
5692+
// private _PyThreadState_UncheckedGet()
5693+
PyThreadState *tstate3 = _PyThreadState_UncheckedGet();
5694+
assert(tstate3 == tstate);
5695+
5696+
// PyThreadState_EnterTracing(), PyThreadState_LeaveTracing()
5697+
PyThreadState_EnterTracing(tstate);
5698+
PyThreadState_LeaveTracing(tstate);
5699+
5700+
// PyThreadState_GetDict(): no tstate argument
5701+
PyObject *dict = PyThreadState_GetDict();
5702+
// PyThreadState_GetDict() API can return NULL if PyDict_New() fails,
5703+
// but it should not occur in practice.
5704+
assert(dict != NULL);
5705+
assert(PyDict_Check(dict));
5706+
// dict is a borrowed reference
5707+
5708+
// private _PyThreadState_GetDict()
5709+
PyObject *dict2 = _PyThreadState_GetDict(tstate);
5710+
assert(dict2 == dict);
5711+
// dict2 is a borrowed reference
5712+
5713+
// PyThreadState_GetInterpreter()
5714+
PyInterpreterState *interp = PyThreadState_GetInterpreter(tstate);
5715+
assert(interp != NULL);
5716+
5717+
// PyThreadState_GetFrame()
5718+
PyFrameObject*frame = PyThreadState_GetFrame(tstate);
5719+
assert(frame != NULL);
5720+
assert(PyFrame_Check(frame));
5721+
Py_DECREF(frame);
5722+
5723+
// PyThreadState_GetID()
5724+
uint64_t id = PyThreadState_GetID(tstate);
5725+
assert(id >= 1);
5726+
5727+
Py_RETURN_NONE;
5728+
}
5729+
5730+
56775731
static PyObject *test_buildvalue_issue38913(PyObject *, PyObject *);
56785732
static PyObject *getargs_s_hash_int(PyObject *, PyObject *, PyObject*);
56795733

@@ -5957,6 +6011,7 @@ static PyMethodDef TestMethods[] = {
59576011
{"fatal_error", test_fatal_error, METH_VARARGS,
59586012
PyDoc_STR("fatal_error(message, release_gil=False): call Py_FatalError(message)")},
59596013
{"type_get_version", type_get_version, METH_O, PyDoc_STR("type->tp_version_tag")},
6014+
{"test_tstate_capi", test_tstate_capi, METH_NOARGS, NULL},
59606015
{NULL, NULL} /* sentinel */
59616016
};
59626017

Python/ceval.c

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6187,7 +6187,7 @@ call_trace(Py_tracefunc func, PyObject *obj,
61876187
if (tstate->tracing)
61886188
return 0;
61896189
tstate->tracing++;
6190-
tstate->cframe->use_tracing = 0;
6190+
_PyThreadState_DisableTracing(tstate);
61916191
PyFrameObject *f = _PyFrame_GetFrameObject(frame);
61926192
if (f == NULL) {
61936193
return -1;
@@ -6201,8 +6201,7 @@ call_trace(Py_tracefunc func, PyObject *obj,
62016201
}
62026202
result = func(obj, f, what, arg);
62036203
f->f_lineno = 0;
6204-
tstate->cframe->use_tracing = ((tstate->c_tracefunc != NULL)
6205-
|| (tstate->c_profilefunc != NULL)) ? 255 : 0;
6204+
_PyThreadState_ResetTracing(tstate);
62066205
tstate->tracing--;
62076206
return result;
62086207
}
@@ -6216,8 +6215,7 @@ _PyEval_CallTracing(PyObject *func, PyObject *args)
62166215
PyObject *result;
62176216

62186217
tstate->tracing = 0;
6219-
tstate->cframe->use_tracing = ((tstate->c_tracefunc != NULL)
6220-
|| (tstate->c_profilefunc != NULL)) ? 255 : 0;
6218+
_PyThreadState_ResetTracing(tstate);
62216219
result = PyObject_Call(func, args, NULL);
62226220
tstate->tracing = save_tracing;
62236221
tstate->cframe->use_tracing = save_use_tracing;
@@ -6274,15 +6272,15 @@ _PyEval_SetProfile(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
62746272
tstate->c_profilefunc = NULL;
62756273
tstate->c_profileobj = NULL;
62766274
/* Must make sure that tracing is not ignored if 'profileobj' is freed */
6277-
tstate->cframe->use_tracing = tstate->c_tracefunc != NULL;
6275+
_PyThreadState_ResetTracing(tstate);
62786276
Py_XDECREF(profileobj);
62796277

62806278
Py_XINCREF(arg);
62816279
tstate->c_profileobj = arg;
62826280
tstate->c_profilefunc = func;
62836281

62846282
/* Flag that tracing or profiling is turned on */
6285-
tstate->cframe->use_tracing = (func != NULL) || (tstate->c_tracefunc != NULL) ? 255 : 0;
6283+
_PyThreadState_ResetTracing(tstate);
62866284
return 0;
62876285
}
62886286

@@ -6315,16 +6313,15 @@ _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
63156313
tstate->c_tracefunc = NULL;
63166314
tstate->c_traceobj = NULL;
63176315
/* Must make sure that profiling is not ignored if 'traceobj' is freed */
6318-
tstate->cframe->use_tracing = (tstate->c_profilefunc != NULL) ? 255 : 0;
6316+
_PyThreadState_ResetTracing(tstate);
63196317
Py_XDECREF(traceobj);
63206318

63216319
Py_XINCREF(arg);
63226320
tstate->c_traceobj = arg;
63236321
tstate->c_tracefunc = func;
63246322

63256323
/* Flag that tracing or profiling is turned on */
6326-
tstate->cframe->use_tracing = ((func != NULL)
6327-
|| (tstate->c_profilefunc != NULL)) ? 255 : 0;
6324+
_PyThreadState_ResetTracing(tstate);
63286325

63296326
return 0;
63306327
}

Python/pystate.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1201,6 +1201,22 @@ PyThreadState_SetAsyncExc(unsigned long id, PyObject *exc)
12011201
}
12021202

12031203

1204+
void
1205+
PyThreadState_EnterTracing(PyThreadState *tstate)
1206+
{
1207+
tstate->tracing++;
1208+
_PyThreadState_DisableTracing(tstate);
1209+
}
1210+
1211+
void
1212+
PyThreadState_LeaveTracing(PyThreadState *tstate)
1213+
{
1214+
tstate->tracing--;
1215+
_PyThreadState_ResetTracing(tstate);
1216+
}
1217+
1218+
1219+
12041220
/* Routines for advanced debuggers, requested by David Beazley.
12051221
Don't use unless you know what you are doing! */
12061222

Python/sysmodule.c

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -256,8 +256,7 @@ sys_audit_tstate(PyThreadState *ts, const char *event,
256256
}
257257

258258
/* Disallow tracing in hooks unless explicitly enabled */
259-
ts->tracing++;
260-
ts->cframe->use_tracing = 0;
259+
PyThreadState_EnterTracing(ts);
261260
while ((hook = PyIter_Next(hooks)) != NULL) {
262261
_Py_IDENTIFIER(__cantrace__);
263262
PyObject *o;
@@ -270,23 +269,20 @@ sys_audit_tstate(PyThreadState *ts, const char *event,
270269
break;
271270
}
272271
if (canTrace) {
273-
ts->cframe->use_tracing = (ts->c_tracefunc || ts->c_profilefunc) ? 255 : 0;
274-
ts->tracing--;
272+
PyThreadState_LeaveTracing(ts);
275273
}
276274
PyObject* args[2] = {eventName, eventArgs};
277275
o = _PyObject_FastCallTstate(ts, hook, args, 2);
278276
if (canTrace) {
279-
ts->tracing++;
280-
ts->cframe->use_tracing = 0;
277+
PyThreadState_EnterTracing(ts);
281278
}
282279
if (!o) {
283280
break;
284281
}
285282
Py_DECREF(o);
286283
Py_CLEAR(hook);
287284
}
288-
ts->cframe->use_tracing = (ts->c_tracefunc || ts->c_profilefunc) ? 255 : 0;
289-
ts->tracing--;
285+
PyThreadState_LeaveTracing(ts);
290286
if (_PyErr_Occurred(ts)) {
291287
goto exit;
292288
}

0 commit comments

Comments
 (0)