8000 gh-93502: Add new C-API functions to trace object creation and destru… · python/cpython@6bcbee0 · GitHub
[go: up one dir, main page]

Skip to content

Commit 6bcbee0

Browse files
authored
gh-93502: Add new C-API functions to trace object creation and destruction (#115945)
1 parent 2770d5c commit 6bcbee0

File tree

10 files changed

+207
-8
lines changed

10 files changed

+207
-8
lines changed

Doc/c-api/init.rst

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1904,6 +1904,58 @@ Python-level trace functions in previous versions.
19041904
19051905
.. versionadded:: 3.12
19061906
1907+
Reference tracing
1908+
=================
1909+
1910+
.. versionadded:: 3.13
1911+
1912+
.. c:type:: int (*PyRefTracer)(PyObject *, int event, void* data)
1913+
1914+
The type of the trace function registered using :c:func:`PyRefTracer_SetTracer`.
1915+
The first parameter is a Python object that has been just created (when **event**
1916+
is set to :c:data:`PyRefTracer_CREATE`) or about to be destroyed (when **event**
1917+
is set to :c:data:`PyRefTracer_DESTROY`). The **data** argument is the opaque pointer
1918+
that was provided when :c:func:`PyRefTracer_SetTracer` was called.
1919+
1920+
.. versionadded:: 3.13
1921+
1922+
.. c:var:: int PyRefTracer_CREATE
1923+
1924+
The value for the *event* parameter to :c:type:`PyRefTracer` functions when a Python
1925+
object has been created.
1926+
1927+
.. c:var:: int PyRefTracer_DESTROY
1928+
1929+
The value for the *event* parameter to :c:type:`PyRefTracer` functions when a Python
1930+
object has been destroyed.
1931+
1932+
.. c:function:: int PyRefTracer_SetTracer(PyRefTracer tracer, void *data)
1933+
1934+
Register a reference tracer function. The function will be called when a new
1935+
Python has been created or when an object is going to be destroyed. If
1936+
**data** is provided it must be an opaque pointer that will be provided when
1937+
the tracer function is called. Return ``0`` on success. Set an exception and
1938+
return ``-1`` on error.
1939+
1940+
Not that tracer functions **must not** create Python objects inside or
1941+
otherwise the call will be re-entrant. The tracer also **must not** clear
1942+
any existing exception or set an exception. The GIL will be held every time
1943+
the tracer function is called.
1944+
1945+
The GIL must be held when calling this function.
1946+
1947+
.. versionadded:: 3.13
1948+
1949+
.. c:function:: PyRefTracer PyRefTracer_GetTracer(void** data)
1950+
1951+
Get the registered reference tracer function and the value of the opaque data
1952+
pointer that was registered when :c:func:`PyRefTracer_SetTracer` was called.
1953+
If no tracer was registered this function will return NULL and will set the
1954+
**data** pointer to NULL.
1955+
1956+
The GIL must be held when calling this function.
1957+
1958+
.. versionadded:: 3.13
19071959
19081960
.. _advanced-debugging:
19091961

Doc/whatsnew/3.13.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1961,6 +1961,11 @@ New Features
19611961
* Add :c:func:`PyType_GetModuleByDef` to the limited C API
19621962
(Contributed by Victor Stinner in :gh:`116936`.)
19631963

1964+
* Add two new functions to the C-API, :c:func:`PyRefTracer_SetTracer` and
1965+
:c:func:`PyRefTracer_GetTracer`, that allows to track object creation and
1966+
destruction the same way the :mod:`tracemalloc` module does. (Contributed
1967+
by Pablo Galindo in :gh:`93502`.)
1968+
19641969

19651970
Porting to Python 3.13
19661971
----------------------

Include/cpython/object.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,3 +510,13 @@ PyAPI_FUNC(int) PyType_Unwatch(int watcher_id, PyObject *type);
510510
* assigned, or 0 if a new tag could not be assigned.
511511
*/
512512
PyAPI_FUNC(int) PyUnstable_Type_AssignVersionTag(PyTypeObject *type);
513+
514+
515+
typedef enum {
516+
PyRefTracer_CREATE = 0,
517+
PyRefTracer_DESTROY = 1,
518+
} PyRefTracerEvent;
519+
520+
typedef int (*PyRefTracer)(PyObject *, PyRefTracerEvent event, void *);
521+
PyAPI_FUNC(int) PyRefTracer_SetTracer(PyRefTracer tracer, void *data);
522+
PyAPI_FUNC(PyRefTracer) PyRefTracer_GetTracer(void**);

Include/internal/pycore_object.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ extern int _PyDict_CheckConsistency(PyObject *mp, int check_content);
257257
when a memory block is reused from a free list.
258258
259259
Internal function called by _Py_NewReference(). */
260-
extern int _PyTraceMalloc_NewReference(PyObject *op);
260+
extern int _PyTraceMalloc_TraceRef(PyObject *op, PyRefTracerEvent event, void*);
261261

262262
// Fast inlined version of PyType_HasFeature()
263263
static inline int

Include/internal/pycore_runtime.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,12 @@ typedef struct _Py_DebugOffsets {
132132
} unicode_object;
133133
} _Py_DebugOffsets;
134134

135+
/* Reference tracer state */
136+
struct _reftracer_runtime_state {
137+
PyRefTracer tracer_func;
138+
void* tracer_data;
139+
};
140+
135141
/* Full Python runtime state */
136142

137143
/* _PyRuntimeState holds the global state for the CPython runtime.
@@ -236,6 +242,7 @@ typedef struct pyruntimestate {
236242
struct _fileutils_state fileutils;
237243
struct _faulthandler_runtime_state faulthandler;
238244
struct _tracemalloc_runtime_state tracemalloc;
245+
struct _reftracer_runtime_state ref_tracer;
239246

240247
// The rwmutex is used to prevent overlapping global and per-interpreter
241248
// stop-the-world events. Global stop-the-world events lock the mutex

Include/internal/pycore_runtime_init.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ extern PyTypeObject _PyExc_MemoryError;
128128
}, \
129129
.faulthandler = _faulthandler_runtime_state_INIT, \
130130
.tracemalloc = _tracemalloc_runtime_state_INIT, \
131+
.ref_tracer = { \
132+
.tracer_func = NULL, \
133+
.tracer_data = NULL, \
134+
}, \
131135
.stoptheworld = { \
132136
.is_global = 1, \
133137
}, \
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Add two new functions to the C-API, :c:func:`PyRefTracer_SetTracer` and
2+
:c:func:`PyRefTracer_GetTracer`, that allows to track object creation and
3+
destruction the same way the :mod:`tracemalloc` module does. Patch by Pablo
4+
Galindo

Modules/_testcapimodule.c

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3219,6 +3219,89 @@ test_weakref_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
32193219
_Py_COMP_DIAG_POP
32203220
}
32213221

3222+
struct simpletracer_data {
3223+
int create_count;
3224+
int destroy_count;
3225+
void* addresses[10];
3226+
};
3227+
3228+
static int _simpletracer(PyObject *obj, PyRefTracerEvent event, void* data) {
3229+
struct simpletracer_data* the_data = (struct simpletracer_data*)data;
3230+
assert(the_data->create_count + the_data->destroy_count < (int)Py_ARRAY_LENGTH(the_data->addresses));
3231+
the_data->addresses[the_data->create_count + the_data->destroy_count] = obj;
3232+
if (event == PyRefTracer_CREATE) {
3233+
the_data->create_count++;
3234+
} else {
3235+
the_data->destroy_count++;
3236+
}
3237+
return 0;
3238+
}
3239+
3240+
static PyObject *
3241+
test_reftracer(PyObject *ob, PyObject *Py_UNUSED(ignored))
3242+
{
3243+
// Save the current tracer and data to restore it later
3244+
void* current_data;
3245+
PyRefTracer current_tracer = PyRefTracer_GetTracer(&current_data);
3246+
3247+
struct simpletracer_data tracer_data = {0};
3248+
void* the_data = &tracer_data;
3249+
// Install a simple tracer function
3250+
if (PyRefTracer_SetTracer(_simpletracer, the_data) != 0) {
3251+
goto failed;
3252+
}
3253+
3254+
// Check that the tracer was correctly installed
3255+
void* data;
3256+
if (PyRefTracer_GetTracer(&data) != _simpletracer || data != the_data) {
3257+
PyErr_SetString(PyExc_AssertionError, "The reftracer not correctly installed");
3258+
(void)PyRefTracer_SetTracer(NULL, NULL);
3259+
goto failed;
3260+
}
3261+
3262+
// Create a bunch of objects
3263+
PyObject* obj = PyList_New(0);
3264+
if (obj == NULL) {
3265+
goto failed;
3266+
}
3267+
PyObject* obj2 = PyDict_New();
3268+
if (obj2 == NULL) {
3269+
Py_DECREF(obj);
3270+
goto failed;
3271+
}
3272+
3273+
// Kill all objects
3274+
Py_DECREF(obj);
3275+
Py_DECREF(obj2);
3276+
3277+
// Remove the tracer
3278+
(void)PyRefTracer_SetTracer(NULL, NULL);
3279+
3280+
// Check that the tracer was removed
3281+
if (PyRefTracer_GetTracer(&data) != NULL || data != NULL) {
3282+
PyErr_SetString(PyExc_ValueError, "The reftracer was not correctly removed");
3283+
goto failed;
3284+
}
3285+
3286+
if (tracer_data.create_count != 2 ||
3287+
tracer_data.addresses[0] != obj ||
3288+
tracer_data.addresses[1] != obj2) {
3289+
PyErr_SetString(PyExc_ValueError, "The object creation was not correctly traced");
3290+
goto failed;
3291+
}
3292+
3293+
if (tracer_data.destroy_count != 2 ||
3294+
tracer_data.addresses[2] != obj ||
3295+
tracer_data.addresses[3] != obj2) {
3296+
PyErr_SetString(PyExc_ValueError, "The object destruction was not correctly traced");
3297+
goto failed;
3298+
}
3299+
PyRefTracer_SetTracer(current_tracer, current_data);
3300+
Py_RETURN_NONE;
3301+
failed:
3302+
PyRefTracer_SetTracer(current_tracer, current_data);
3303+
return NULL;
3304+
}
32223305

32233306
static PyMethodDef TestMethods[] = {
32243307
{"set_errno", set_errno, METH_VARARGS},
@@ -3257,6 +3340,7 @@ static PyMethodDef TestMethods[] = {
32573340
{"get_type_fullyqualname", get_type_fullyqualname, METH_O},
32583341
{"get_type_module_name", get_type_module_name, METH_O},
32593342
{"test_get_type_dict", test_get_type_dict, METH_NOARGS},
3343+
{"test_reftracer", test_reftracer, METH_NOARGS},
32603344 F438
{"_test_thread_state", test_thread_state, METH_VARARGS},
32613345
#ifndef MS_WINDOWS
32623346
{"_spawn_pthread_waiter", spawn_pthread_waiter, METH_NOARGS},

Objects/object.c

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2372,9 +2372,6 @@ _PyTypes_FiniTypes(PyInterpreterState *interp)
23722372
static inline void
23732373
new_reference(PyObject *op)
23742374
{
2375-
if (_PyRuntime.tracemalloc.config.tracing) {
2376-
_PyTraceMalloc_NewReference(op);
2377-
}
23782375
// Skip the immortal object check in Py_SET_REFCNT; always set refcnt to 1
23792376
#if !defined(Py_GIL_DISABLED)
23802377
op->ob_refcnt = 1;
@@ -2389,6 +2386,11 @@ new_reference(PyObject *op)
23892386
#ifdef Py_TRACE_REFS
23902387
_Py_AddToAllObjects(op);
23912388
#endif
2389+
struct _reftracer_runtime_state *tracer = &_PyRuntime.ref_tracer;
2390+
if (tracer->tracer_func != NULL) {
2391+
void* data = tracer->tracer_data;
2392+
tracer->tracer_func(op, PyRefTracer_CREATE, data);
2393+
}
23922394
}
23932395

23942396
void
@@ -2450,12 +2452,13 @@ _PyObject_SetDeferredRefcount(PyObject *op)
24502452
void
24512453
_Py_ResurrectReference(PyObject *op)
24522454
{
2453-
if (_PyRuntime.tracemalloc.config.tracing) {
2454-
_PyTraceMalloc_NewReference(op);
2455-
}
24562455
#ifdef Py_TRACE_REFS
24572456
_Py_AddToAllObjects(op);
24582457
#endif
2458+
if (_PyRuntime.ref_tracer.tracer_func != NULL) {
2459+
void* data = _PyRuntime.ref_tracer.tracer_data;
2460+
_PyRuntime.ref_tracer.tracer_func(op, PyRefTracer_CREATE, data);
2461+
}
24592462
}
24602463

24612464

@@ -2845,6 +2848,12 @@ _Py_Dealloc(PyObject *op)
28452848
Py_INCREF(type);
28462849
#endif
28472850

2851+
struct _reftracer_runtime_state *tracer = &_PyRuntime.ref_tracer;
2852+
if (tracer->tracer_func != NULL) {
2853+
void* data = tracer->tracer_data;
2854+
tracer->tracer_func(op, PyRefTracer_DESTROY, data);
2855+
}
2856+
28482857
#ifdef Py_TRACE_REFS
28492858
_Py_ForgetReference(op);
28502859
#endif
@@ -2933,6 +2942,22 @@ _Py_SetRefcnt(PyObject *ob, Py_ssize_t refcnt)
29332942
Py_SET_REFCNT(ob, refcnt);
29342943
}
29352944

2945+
int PyRefTracer_SetTracer(PyRefTracer tracer, void *data) {
2946+
assert(PyGILState_Check());
2947+
_PyRuntime.ref_tracer.tracer_func = tracer;
2948+
_PyRuntime.ref_tracer.tracer_data = data;
2949+
return 0;
2950+
}
2951+
2952+
PyRefTracer PyRefTracer_GetTracer(void** data) {
2953+
assert(PyGILState_Check());
2954+
if (data != NULL) {
2955+
*data = _PyRuntime.ref_tracer.tracer_data;
2956+
}
2957+
return _PyRuntime.ref_tracer.tracer_func;
2958+
}
2959+
2960+
29362961

29372962
static PyObject* constants[] = {
29382963
&_Py_NoneStruct, // Py_CONSTANT_NONE

Python/tracemalloc.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,10 @@ _PyTraceMalloc_Start(int max_nframe)
906906
return -1;
907907
}
908908

909+
if (PyRefTracer_SetTracer(_PyTraceMalloc_TraceRef, NULL) < 0) {
910+
return -1;
911+
}
912+
909913
if (tracemalloc_config.tracing) {
910914
/* hook already installed: do nothing */
911915
return 0;
@@ -1352,8 +1356,12 @@ _PyTraceMalloc_Fini(void)
13521356
Do nothing if tracemalloc is not tracing memory allocations
13531357
or if the object memory block is not already traced. */
13541358
int
1355-
_PyTraceMalloc_NewReference(PyObject *op)
1359+
_PyTraceMalloc_TraceRef(PyObject *op, PyRefTracerEvent event, void* Py_UNUSED(ignore))
13561360
{
1361+
if (event != PyRefTracer_CREATE) {
1362+
return 0;
1363+
}
1364+
13571365
assert(PyGILState_Check());
13581366

13591367
if (!tracemalloc_config.tracing) {

0 commit comments

Comments
 (0)
0