mirror of https://github.com/python/cpython
gh-93502: Add new C-API functions to trace object creation and destruction (#115945)
This commit is contained in:
parent
2770d5caca
commit
6bcbee09df
|
@ -1904,6 +1904,58 @@ Python-level trace functions in previous versions.
|
||||||
|
|
||||||
.. versionadded:: 3.12
|
.. versionadded:: 3.12
|
||||||
|
|
||||||
|
Reference tracing
|
||||||
|
=================
|
||||||
|
|
||||||
|
.. versionadded:: 3.13
|
||||||
|
|
||||||
|
.. c:type:: int (*PyRefTracer)(PyObject *, int event, void* data)
|
||||||
|
|
||||||
|
The type of the trace function registered using :c:func:`PyRefTracer_SetTracer`.
|
||||||
|
The first parameter is a Python object that has been just created (when **event**
|
||||||
|
is set to :c:data:`PyRefTracer_CREATE`) or about to be destroyed (when **event**
|
||||||
|
is set to :c:data:`PyRefTracer_DESTROY`). The **data** argument is the opaque pointer
|
||||||
|
that was provided when :c:func:`PyRefTracer_SetTracer` was called.
|
||||||
|
|
||||||
|
.. versionadded:: 3.13
|
||||||
|
|
||||||
|
.. c:var:: int PyRefTracer_CREATE
|
||||||
|
|
||||||
|
The value for the *event* parameter to :c:type:`PyRefTracer` functions when a Python
|
||||||
|
object has been created.
|
||||||
|
|
||||||
|
.. c:var:: int PyRefTracer_DESTROY
|
||||||
|
|
||||||
|
The value for the *event* parameter to :c:type:`PyRefTracer` functions when a Python
|
||||||
|
object has been destroyed.
|
||||||
|
|
||||||
|
.. c:function:: int PyRefTracer_SetTracer(PyRefTracer tracer, void *data)
|
||||||
|
|
||||||
|
Register a reference tracer function. The function will be called when a new
|
||||||
|
Python has been created or when an object is going to be destroyed. If
|
||||||
|
**data** is provided it must be an opaque pointer that will be provided when
|
||||||
|
the tracer function is called. Return ``0`` on success. Set an exception and
|
||||||
|
return ``-1`` on error.
|
||||||
|
|
||||||
|
Not that tracer functions **must not** create Python objects inside or
|
||||||
|
otherwise the call will be re-entrant. The tracer also **must not** clear
|
||||||
|
any existing exception or set an exception. The GIL will be held every time
|
||||||
|
the tracer function is called.
|
||||||
|
|
||||||
|
The GIL must be held when calling this function.
|
||||||
|
|
||||||
|
.. versionadded:: 3.13
|
||||||
|
|
||||||
|
.. c:function:: PyRefTracer PyRefTracer_GetTracer(void** data)
|
||||||
|
|
||||||
|
Get the registered reference tracer function and the value of the opaque data
|
||||||
|
pointer that was registered when :c:func:`PyRefTracer_SetTracer` was called.
|
||||||
|
If no tracer was registered this function will return NULL and will set the
|
||||||
|
**data** pointer to NULL.
|
||||||
|
|
||||||
|
The GIL must be held when calling this function.
|
||||||
|
|
||||||
|
.. versionadded:: 3.13
|
||||||
|
|
||||||
.. _advanced-debugging:
|
.. _advanced-debugging:
|
||||||
|
|
||||||
|
|
|
@ -1961,6 +1961,11 @@ New Features
|
||||||
* Add :c:func:`PyType_GetModuleByDef` to the limited C API
|
* Add :c:func:`PyType_GetModuleByDef` to the limited C API
|
||||||
(Contributed by Victor Stinner in :gh:`116936`.)
|
(Contributed by Victor Stinner in :gh:`116936`.)
|
||||||
|
|
||||||
|
* Add two new functions to the C-API, :c:func:`PyRefTracer_SetTracer` and
|
||||||
|
:c:func:`PyRefTracer_GetTracer`, that allows to track object creation and
|
||||||
|
destruction the same way the :mod:`tracemalloc` module does. (Contributed
|
||||||
|
by Pablo Galindo in :gh:`93502`.)
|
||||||
|
|
||||||
|
|
||||||
Porting to Python 3.13
|
Porting to Python 3.13
|
||||||
----------------------
|
----------------------
|
||||||
|
|
|
@ -510,3 +510,13 @@ PyAPI_FUNC(int) PyType_Unwatch(int watcher_id, PyObject *type);
|
||||||
* assigned, or 0 if a new tag could not be assigned.
|
* assigned, or 0 if a new tag could not be assigned.
|
||||||
*/
|
*/
|
||||||
PyAPI_FUNC(int) PyUnstable_Type_AssignVersionTag(PyTypeObject *type);
|
PyAPI_FUNC(int) PyUnstable_Type_AssignVersionTag(PyTypeObject *type);
|
||||||
|
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
PyRefTracer_CREATE = 0,
|
||||||
|
PyRefTracer_DESTROY = 1,
|
||||||
|
} PyRefTracerEvent;
|
||||||
|
|
||||||
|
typedef int (*PyRefTracer)(PyObject *, PyRefTracerEvent event, void *);
|
||||||
|
PyAPI_FUNC(int) PyRefTracer_SetTracer(PyRefTracer tracer, void *data);
|
||||||
|
PyAPI_FUNC(PyRefTracer) PyRefTracer_GetTracer(void**);
|
||||||
|
|
|
@ -257,7 +257,7 @@ extern int _PyDict_CheckConsistency(PyObject *mp, int check_content);
|
||||||
when a memory block is reused from a free list.
|
when a memory block is reused from a free list.
|
||||||
|
|
||||||
Internal function called by _Py_NewReference(). */
|
Internal function called by _Py_NewReference(). */
|
||||||
extern int _PyTraceMalloc_NewReference(PyObject *op);
|
extern int _PyTraceMalloc_TraceRef(PyObject *op, PyRefTracerEvent event, void*);
|
||||||
|
|
||||||
// Fast inlined version of PyType_HasFeature()
|
// Fast inlined version of PyType_HasFeature()
|
||||||
static inline int
|
static inline int
|
||||||
|
|
|
@ -132,6 +132,12 @@ typedef struct _Py_DebugOffsets {
|
||||||
} unicode_object;
|
} unicode_object;
|
||||||
} _Py_DebugOffsets;
|
} _Py_DebugOffsets;
|
||||||
|
|
||||||
|
/* Reference tracer state */
|
||||||
|
struct _reftracer_runtime_state {
|
||||||
|
PyRefTracer tracer_func;
|
||||||
|
void* tracer_data;
|
||||||
|
};
|
||||||
|
|
||||||
/* Full Python runtime state */
|
/* Full Python runtime state */
|
||||||
|
|
||||||
/* _PyRuntimeState holds the global state for the CPython runtime.
|
/* _PyRuntimeState holds the global state for the CPython runtime.
|
||||||
|
@ -236,6 +242,7 @@ typedef struct pyruntimestate {
|
||||||
struct _fileutils_state fileutils;
|
struct _fileutils_state fileutils;
|
||||||
struct _faulthandler_runtime_state faulthandler;
|
struct _faulthandler_runtime_state faulthandler;
|
||||||
struct _tracemalloc_runtime_state tracemalloc;
|
struct _tracemalloc_runtime_state tracemalloc;
|
||||||
|
struct _reftracer_runtime_state ref_tracer;
|
||||||
|
|
||||||
// The rwmutex is used to prevent overlapping global and per-interpreter
|
// The rwmutex is used to prevent overlapping global and per-interpreter
|
||||||
// stop-the-world events. Global stop-the-world events lock the mutex
|
// stop-the-world events. Global stop-the-world events lock the mutex
|
||||||
|
|
|
@ -128,6 +128,10 @@ extern PyTypeObject _PyExc_MemoryError;
|
||||||
}, \
|
}, \
|
||||||
.faulthandler = _faulthandler_runtime_state_INIT, \
|
.faulthandler = _faulthandler_runtime_state_INIT, \
|
||||||
.tracemalloc = _tracemalloc_runtime_state_INIT, \
|
.tracemalloc = _tracemalloc_runtime_state_INIT, \
|
||||||
|
.ref_tracer = { \
|
||||||
|
.tracer_func = NULL, \
|
||||||
|
.tracer_data = NULL, \
|
||||||
|
}, \
|
||||||
.stoptheworld = { \
|
.stoptheworld = { \
|
||||||
.is_global = 1, \
|
.is_global = 1, \
|
||||||
}, \
|
}, \
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Add two new functions to the C-API, :c:func:`PyRefTracer_SetTracer` and
|
||||||
|
:c:func:`PyRefTracer_GetTracer`, that allows to track object creation and
|
||||||
|
destruction the same way the :mod:`tracemalloc` module does. Patch by Pablo
|
||||||
|
Galindo
|
|
@ -3219,6 +3219,89 @@ test_weakref_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
|
||||||
_Py_COMP_DIAG_POP
|
_Py_COMP_DIAG_POP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct simpletracer_data {
|
||||||
|
int create_count;
|
||||||
|
int destroy_count;
|
||||||
|
void* addresses[10];
|
||||||
|
};
|
||||||
|
|
||||||
|
static int _simpletracer(PyObject *obj, PyRefTracerEvent event, void* data) {
|
||||||
|
struct simpletracer_data* the_data = (struct simpletracer_data*)data;
|
||||||
|
assert(the_data->create_count + the_data->destroy_count < (int)Py_ARRAY_LENGTH(the_data->addresses));
|
||||||
|
the_data->addresses[the_data->create_count + the_data->destroy_count] = obj;
|
||||||
|
if (event == PyRefTracer_CREATE) {
|
||||||
|
the_data->create_count++;
|
||||||
|
} else {
|
||||||
|
the_data->destroy_count++;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
test_reftracer(PyObject *ob, PyObject *Py_UNUSED(ignored))
|
||||||
|
{
|
||||||
|
// Save the current tracer and data to restore it later
|
||||||
|
void* current_data;
|
||||||
|
PyRefTracer current_tracer = PyRefTracer_GetTracer(¤t_data);
|
||||||
|
|
||||||
|
struct simpletracer_data tracer_data = {0};
|
||||||
|
void* the_data = &tracer_data;
|
||||||
|
// Install a simple tracer function
|
||||||
|
if (PyRefTracer_SetTracer(_simpletracer, the_data) != 0) {
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the tracer was correctly installed
|
||||||
|
void* data;
|
||||||
|
if (PyRefTracer_GetTracer(&data) != _simpletracer || data != the_data) {
|
||||||
|
PyErr_SetString(PyExc_AssertionError, "The reftracer not correctly installed");
|
||||||
|
(void)PyRefTracer_SetTracer(NULL, NULL);
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a bunch of objects
|
||||||
|
PyObject* obj = PyList_New(0);
|
||||||
|
if (obj == NULL) {
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
PyObject* obj2 = PyDict_New();
|
||||||
|
if (obj2 == NULL) {
|
||||||
|
Py_DECREF(obj);
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kill all objects
|
||||||
|
Py_DECREF(obj);
|
||||||
|
Py_DECREF(obj2);
|
||||||
|
|
||||||
|
// Remove the tracer
|
||||||
|
(void)PyRefTracer_SetTracer(NULL, NULL);
|
||||||
|
|
||||||
|
// Check that the tracer was removed
|
||||||
|
if (PyRefTracer_GetTracer(&data) != NULL || data != NULL) {
|
||||||
|
PyErr_SetString(PyExc_ValueError, "The reftracer was not correctly removed");
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tracer_data.create_count != 2 ||
|
||||||
|
tracer_data.addresses[0] != obj ||
|
||||||
|
tracer_data.addresses[1] != obj2) {
|
||||||
|
PyErr_SetString(PyExc_ValueError, "The object creation was not correctly traced");
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tracer_data.destroy_count != 2 ||
|
||||||
|
tracer_data.addresses[2] != obj ||
|
||||||
|
tracer_data.addresses[3] != obj2) {
|
||||||
|
PyErr_SetString(PyExc_ValueError, "The object destruction was not correctly traced");
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
PyRefTracer_SetTracer(current_tracer, current_data);
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
failed:
|
||||||
|
PyRefTracer_SetTracer(current_tracer, current_data);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static PyMethodDef TestMethods[] = {
|
static PyMethodDef TestMethods[] = {
|
||||||
{"set_errno", set_errno, METH_VARARGS},
|
{"set_errno", set_errno, METH_VARARGS},
|
||||||
|
@ -3257,6 +3340,7 @@ static PyMethodDef TestMethods[] = {
|
||||||
{"get_type_fullyqualname", get_type_fullyqualname, METH_O},
|
{"get_type_fullyqualname", get_type_fullyqualname, METH_O},
|
||||||
{"get_type_module_name", get_type_module_name, METH_O},
|
{"get_type_module_name", get_type_module_name, METH_O},
|
||||||
{"test_get_type_dict", test_get_type_dict, METH_NOARGS},
|
{"test_get_type_dict", test_get_type_dict, METH_NOARGS},
|
||||||
|
{"test_reftracer", test_reftracer, METH_NOARGS},
|
||||||
{"_test_thread_state", test_thread_state, METH_VARARGS},
|
{"_test_thread_state", test_thread_state, METH_VARARGS},
|
||||||
#ifndef MS_WINDOWS
|
#ifndef MS_WINDOWS
|
||||||
{"_spawn_pthread_waiter", spawn_pthread_waiter, METH_NOARGS},
|
{"_spawn_pthread_waiter", spawn_pthread_waiter, METH_NOARGS},
|
||||||
|
|
|
@ -2372,9 +2372,6 @@ _PyTypes_FiniTypes(PyInterpreterState *interp)
|
||||||
static inline void
|
static inline void
|
||||||
new_reference(PyObject *op)
|
new_reference(PyObject *op)
|
||||||
{
|
{
|
||||||
if (_PyRuntime.tracemalloc.config.tracing) {
|
|
||||||
_PyTraceMalloc_NewReference(op);
|
|
||||||
}
|
|
||||||
// Skip the immortal object check in Py_SET_REFCNT; always set refcnt to 1
|
// Skip the immortal object check in Py_SET_REFCNT; always set refcnt to 1
|
||||||
#if !defined(Py_GIL_DISABLED)
|
#if !defined(Py_GIL_DISABLED)
|
||||||
op->ob_refcnt = 1;
|
op->ob_refcnt = 1;
|
||||||
|
@ -2389,6 +2386,11 @@ new_reference(PyObject *op)
|
||||||
#ifdef Py_TRACE_REFS
|
#ifdef Py_TRACE_REFS
|
||||||
_Py_AddToAllObjects(op);
|
_Py_AddToAllObjects(op);
|
||||||
#endif
|
#endif
|
||||||
|
struct _reftracer_runtime_state *tracer = &_PyRuntime.ref_tracer;
|
||||||
|
if (tracer->tracer_func != NULL) {
|
||||||
|
void* data = tracer->tracer_data;
|
||||||
|
tracer->tracer_func(op, PyRefTracer_CREATE, data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -2450,12 +2452,13 @@ _PyObject_SetDeferredRefcount(PyObject *op)
|
||||||
void
|
void
|
||||||
_Py_ResurrectReference(PyObject *op)
|
_Py_ResurrectReference(PyObject *op)
|
||||||
{
|
{
|
||||||
if (_PyRuntime.tracemalloc.config.tracing) {
|
|
||||||
_PyTraceMalloc_NewReference(op);
|
|
||||||
}
|
|
||||||
#ifdef Py_TRACE_REFS
|
#ifdef Py_TRACE_REFS
|
||||||
_Py_AddToAllObjects(op);
|
_Py_AddToAllObjects(op);
|
||||||
#endif
|
#endif
|
||||||
|
if (_PyRuntime.ref_tracer.tracer_func != NULL) {
|
||||||
|
void* data = _PyRuntime.ref_tracer.tracer_data;
|
||||||
|
_PyRuntime.ref_tracer.tracer_func(op, PyRefTracer_CREATE, data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -2845,6 +2848,12 @@ _Py_Dealloc(PyObject *op)
|
||||||
Py_INCREF(type);
|
Py_INCREF(type);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
struct _reftracer_runtime_state *tracer = &_PyRuntime.ref_tracer;
|
||||||
|
if (tracer->tracer_func != NULL) {
|
||||||
|
void* data = tracer->tracer_data;
|
||||||
|
tracer->tracer_func(op, PyRefTracer_DESTROY, data);
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef Py_TRACE_REFS
|
#ifdef Py_TRACE_REFS
|
||||||
_Py_ForgetReference(op);
|
_Py_ForgetReference(op);
|
||||||
#endif
|
#endif
|
||||||
|
@ -2933,6 +2942,22 @@ _Py_SetRefcnt(PyObject *ob, Py_ssize_t refcnt)
|
||||||
Py_SET_REFCNT(ob, refcnt);
|
Py_SET_REFCNT(ob, refcnt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int PyRefTracer_SetTracer(PyRefTracer tracer, void *data) {
|
||||||
|
assert(PyGILState_Check());
|
||||||
|
_PyRuntime.ref_tracer.tracer_func = tracer;
|
||||||
|
_PyRuntime.ref_tracer.tracer_data = data;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyRefTracer PyRefTracer_GetTracer(void** data) {
|
||||||
|
assert(PyGILState_Check());
|
||||||
|
if (data != NULL) {
|
||||||
|
*data = _PyRuntime.ref_tracer.tracer_data;
|
||||||
|
}
|
||||||
|
return _PyRuntime.ref_tracer.tracer_func;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static PyObject* constants[] = {
|
static PyObject* constants[] = {
|
||||||
&_Py_NoneStruct, // Py_CONSTANT_NONE
|
&_Py_NoneStruct, // Py_CONSTANT_NONE
|
||||||
|
|
|
@ -906,6 +906,10 @@ _PyTraceMalloc_Start(int max_nframe)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (PyRefTracer_SetTracer(_PyTraceMalloc_TraceRef, NULL) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
if (tracemalloc_config.tracing) {
|
if (tracemalloc_config.tracing) {
|
||||||
/* hook already installed: do nothing */
|
/* hook already installed: do nothing */
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -1352,8 +1356,12 @@ _PyTraceMalloc_Fini(void)
|
||||||
Do nothing if tracemalloc is not tracing memory allocations
|
Do nothing if tracemalloc is not tracing memory allocations
|
||||||
or if the object memory block is not already traced. */
|
or if the object memory block is not already traced. */
|
||||||
int
|
int
|
||||||
_PyTraceMalloc_NewReference(PyObject *op)
|
_PyTraceMalloc_TraceRef(PyObject *op, PyRefTracerEvent event, void* Py_UNUSED(ignore))
|
||||||
{
|
{
|
||||||
|
if (event != PyRefTracer_CREATE) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
assert(PyGILState_Check());
|
assert(PyGILState_Check());
|
||||||
|
|
||||||
if (!tracemalloc_config.tracing) {
|
if (!tracemalloc_config.tracing) {
|
||||||
|
|
Loading…
Reference in New Issue