Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitae9c004

Browse files
committed
gh-93502: Add new C-API functions to trace object creation and
destruction
1 parent686ec17 commitae9c004

File tree

9 files changed

+179
-8
lines changed

9 files changed

+179
-8
lines changed

‎Doc/c-api/init.rst

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1746,6 +1746,48 @@ Python-level trace functions in previous versions.
17461746
17471747
.. versionadded:: 3.12
17481748
1749+
.. c:type:: int (*PyRefTracer)(PyObject *, int event, void* data)
1750+
1751+
The type of the trace function registered using :c:func:`PyRefTracer_SetTracer`
1752+
The first parameter is a Python object that has been just created (when **event**
1753+
is set to :c:data:`PyRefTracer_CREATE`) or about to be destroyed (when **event**
1754+
is set to :c:data:`PyRefTracer_DESTROY`). The **data** argument is the opaque pointer
1755+
that was provided when :c:func:`PyRefTracer_SetTracer` was called.
1756+
1757+
.. versionadded:: 3.13
1758+
1759+
.. c:var:: int PyRefTracer_CREATE
1760+
1761+
The value for the *event* parameter to :c:type:`PyRefTracer` functions when a Python
1762+
object has been created.
1763+
1764+
.. c:var:: int PyRefTracer_DESTROY
1765+
1766+
The value for the *event* parameter to :c:type:`PyRefTracer` functions when a Python
1767+
object has been destroyed.
1768+
1769+
.. c:function:: int PyRefTracer_SetTracer(PyRefTracer tracer, void *data)
1770+
1771+
Register a reference tracer function. The function will be called when a new Python
1772+
has been created or when an object is going to be destroyed. If **data** is provided
1773+
it must be an opaque pointer that will be provided when the tracer function is called.
1774+
1775+
Not that tracer functions **must not** create Python objects inside or otherwise the
1776+
call will be re-entrant.
1777+
1778+
The GIL must be held when calling this function.
1779+
1780+
.. versionadded:: 3.13
1781+
1782+
.. c:function:: PyRefTracer PyRefTracer_GetTracer(void** data)
1783+
1784+
Get the registered reference tracer function and the value of the opaque data pointer that
1785+
was registered when :c:func:`PyRefTracer_SetTracer` was called. If no tracer was registered
1786+
this function will return NULL and will set the **data** pointer to NULL.
1787+
1788+
The GIL must be held when calling this function.
1789+
1790+
.. versionadded:: 3.13
17491791
17501792
.. _advanced-debugging:
17511793

‎Include/cpython/object.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,3 +507,13 @@ PyAPI_FUNC(int) PyType_Unwatch(int watcher_id, PyObject *type);
507507
* assigned, or 0 if a new tag could not be assigned.
508508
*/
509509
PyAPI_FUNC(int)PyUnstable_Type_AssignVersionTag(PyTypeObject*type);
510+
511+
512+
typedefenum {
513+
PyRefTracer_CREATE=0,
514+
PyRefTracer_DESTROY=1,
515+
}PyRefTracerEvent;
516+
517+
typedefint (*PyRefTracer)(PyObject*,PyRefTracerEventevent,void*);
518+
PyAPI_FUNC(int)PyRefTracer_SetTracer(PyRefTracertracer,void*data);
519+
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
@@ -252,7 +252,7 @@ extern int _PyDict_CheckConsistency(PyObject *mp, int check_content);
252252
when a memory block is reused from a free list.
253253
254254
Internal function called by _Py_NewReference(). */
255-
externint_PyTraceMalloc_NewReference(PyObject*op);
255+
externint_PyTraceMalloc_NewReference(PyObject*op,PyRefTracerEventevent,void*);
256256

257257
// Fast inlined version of PyType_HasFeature()
258258
staticinlineint

‎Include/internal/pycore_runtime.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,12 @@ typedef struct _Py_DebugOffsets {
125125
}tuple_object;
126126
}_Py_DebugOffsets;
127127

128+
/* Reference tracer state */
129+
struct_reftracer_runtime_state {
130+
PyRefTracertracer_func;
131+
void*data;
132+
};
133+
128134
/* Full Python runtime state */
129135

130136
/* _PyRuntimeState holds the global state for the CPython runtime.
@@ -229,6 +235,7 @@ typedef struct pyruntimestate {
229235
struct_fileutils_statefileutils;
230236
struct_faulthandler_runtime_statefaulthandler;
231237
struct_tracemalloc_runtime_statetracemalloc;
238+
struct_reftracer_runtime_statereftracer;
232239

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

‎Include/internal/pycore_runtime_init.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ extern PyTypeObject _PyExc_MemoryError;
119119
}, \
120120
.faulthandler = _faulthandler_runtime_state_INIT, \
121121
.tracemalloc = _tracemalloc_runtime_state_INIT, \
122+
.reftracer = { \
123+
.tracer_func = NULL, \
124+
}, \
122125
.stoptheworld = { \
123126
.is_global = 1, \
124127
}, \
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: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3284,6 +3284,80 @@ test_weakref_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
32843284
_Py_COMP_DIAG_POP
32853285
}
32863286

3287+
structsimpletracer_data {
3288+
intcreate_count;
3289+
intdestroy_count;
3290+
void*addresses[10];
3291+
};
3292+
3293+
staticint_simpletracer(PyObject*obj,PyRefTracerEventevent,void*data) {
3294+
structsimpletracer_data*the_data= (structsimpletracer_data*)data;
3295+
assert(the_data->create_count+the_data->destroy_count<10);
3296+
the_data->addresses[the_data->create_count+the_data->destroy_count]=obj;
3297+
if (event==PyRefTracer_CREATE) {
3298+
the_data->create_count++;
3299+
}else {
3300+
the_data->destroy_count++;
3301+
}
3302+
return0;
3303+
}
3304+
3305+
staticPyObject*
3306+
test_reftracer(PyObject*ob,PyObject*Py_UNUSED(ignored))
3307+
{
3308+
structsimpletracer_datatracer_data= {0};
3309+
void*the_data= (void*)&tracer_data;
3310+
// Install a simple tracer function
3311+
PyRefTracer_SetTracer(_simpletracer,the_data);
3312+
3313+
// Check that the tracer was correctly installed
3314+
void*data;
3315+
if (PyRefTracer_GetTracer(&data)!=_simpletracer||data!=the_data) {
3316+
PyErr_SetString(PyExc_ValueError,"The reftracer not correctly installed");
3317+
PyRefTracer_SetTracer(NULL,NULL);
3318+
returnNULL;
3319+
}
3320+
3321+
// Create a bunch of objects
3322+
PyObject*obj=PyList_New(0);
3323+
if (obj==NULL) {
3324+
returnNULL;
3325+
}
3326+
PyObject*obj2=PyDict_New();
3327+
if (obj2==NULL) {
3328+
Py_DECREF(obj);
3329+
returnNULL;
3330+
}
3331+
3332+
// Kill all objects
3333+
Py_DECREF(obj);
3334+
Py_DECREF(obj2);
3335+
3336+
// Remove the tracer
3337+
PyRefTracer_SetTracer(NULL,NULL);
3338+
3339+
// Check that the tracer was removed
3340+
if (PyRefTracer_GetTracer(&data)!=NULL||data!=NULL) {
3341+
PyErr_SetString(PyExc_ValueError,"The reftracer was not correctly removed");
3342+
returnNULL;
3343+
}
3344+
3345+
if (tracer_data.create_count!=2||
3346+
tracer_data.addresses[0]!=obj||
3347+
tracer_data.addresses[1]!=obj2) {
3348+
PyErr_SetString(PyExc_ValueError,"The object creation was not correctly traced");
3349+
returnNULL;
3350+
}
3351+
3352+
if (tracer_data.destroy_count!=2||
3353+
tracer_data.addresses[2]!=obj||
3354+
tracer_data.addresses[3]!=obj2) {
3355+
PyErr_SetString(PyExc_ValueError,"The object destruction was not correctly traced");
3356+
returnNULL;
3357+
}
3358+
3359+
Py_RETURN_NONE;
3360+
}
32873361

32883362
staticPyMethodDefTestMethods[]= {
32893363
{"set_errno",set_errno,METH_VARARGS},
@@ -3320,6 +3394,7 @@ static PyMethodDef TestMethods[] = {
33203394
{"test_get_type_name",test_get_type_name,METH_NOARGS},
33213395
{"test_get_type_qualname",test_get_type_qualname,METH_NOARGS},
33223396
{"test_get_type_dict",test_get_type_dict,METH_NOARGS},
3397+
{"test_reftracer",test_reftracer,METH_NOARGS},
33233398
{"_test_thread_state",test_thread_state,METH_VARARGS},
33243399
#ifndefMS_WINDOWS
33253400
{"_spawn_pthread_waiter",spawn_pthread_waiter,METH_NOARGS},

‎Objects/object.c

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2367,9 +2367,6 @@ _PyTypes_FiniTypes(PyInterpreterState *interp)
23672367
staticinlinevoid
23682368
new_reference(PyObject*op)
23692369
{
2370-
if (_PyRuntime.tracemalloc.config.tracing) {
2371-
_PyTraceMalloc_NewReference(op);
2372-
}
23732370
// Skip the immortal object check in Py_SET_REFCNT; always set refcnt to 1
23742371
#if !defined(Py_GIL_DISABLED)
23752372
op->ob_refcnt=1;
@@ -2384,6 +2381,10 @@ new_reference(PyObject *op)
23842381
#ifdefPy_TRACE_REFS
23852382
_Py_AddToAllObjects(op);
23862383
#endif
2384+
if (_PyRuntime.reftracer.tracer_func!=NULL) {
2385+
void*data=_PyRuntime.reftracer.data;
2386+
_PyRuntime.reftracer.tracer_func(op,PyRefTracer_CREATE,data);
2387+
}
23872388
}
23882389

23892390
void
@@ -2404,12 +2405,13 @@ _Py_NewReferenceNoTotal(PyObject *op)
24042405
void
24052406
_Py_ResurrectReference(PyObject*op)
24062407
{
2407-
if (_PyRuntime.tracemalloc.config.tracing) {
2408-
_PyTraceMalloc_NewReference(op);
2409-
}
24102408
#ifdefPy_TRACE_REFS
24112409
_Py_AddToAllObjects(op);
24122410
#endif
2411+
if (_PyRuntime.reftracer.tracer_func!=NULL) {
2412+
void*data=_PyRuntime.reftracer.data;
2413+
_PyRuntime.reftracer.tracer_func(op,PyRefTracer_CREATE,data);
2414+
}
24132415
}
24142416

24152417

@@ -2883,6 +2885,11 @@ _Py_Dealloc(PyObject *op)
28832885
Py_INCREF(type);
28842886
#endif
28852887

2888+
if (_PyRuntime.reftracer.tracer_func!=NULL) {
2889+
void*data=_PyRuntime.reftracer.data;
2890+
_PyRuntime.reftracer.tracer_func(op,PyRefTracer_DESTROY,data);
2891+
}
2892+
28862893
#ifdefPy_TRACE_REFS
28872894
_Py_ForgetReference(op);
28882895
#endif
@@ -2970,3 +2977,19 @@ _Py_SetRefcnt(PyObject *ob, Py_ssize_t refcnt)
29702977
{
29712978
Py_SET_REFCNT(ob,refcnt);
29722979
}
2980+
2981+
intPyRefTracer_SetTracer(PyRefTracertracer,void*data) {
2982+
assert(PyGILState_Check());
2983+
_PyRuntime.reftracer.tracer_func=tracer;
2984+
_PyRuntime.reftracer.data=data;
2985+
return0;
2986+
}
2987+
2988+
PyRefTracerPyRefTracer_GetTracer(void**data) {
2989+
assert(PyGILState_Check());
2990+
if (data!=NULL) {
2991+
*data=_PyRuntime.reftracer.data;
2992+
}
2993+
return_PyRuntime.reftracer.tracer_func;
2994+
}
2995+

‎Python/tracemalloc.c

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

909+
_PyRuntime.reftracer.tracer_func=_PyTraceMalloc_NewReference;
910+
909911
if (tracemalloc_config.tracing) {
910912
/* hook already installed: do nothing */
911913
return0;
@@ -1352,8 +1354,13 @@ _PyTraceMalloc_Fini(void)
13521354
Do nothing if tracemalloc is not tracing memory allocations
13531355
or if the object memory block is not already traced. */
13541356
int
1355-
_PyTraceMalloc_NewReference(PyObject*op)
1357+
_PyTraceMalloc_NewReference(PyObject*op,PyRefTracerEventevent,void*Py_UNUSED(ignore))
1358+
13561359
{
1360+
if (event!=PyRefTracer_CREATE) {
1361+
return0;
1362+
}
1363+
13571364
assert(PyGILState_Check());
13581365

13591366
if (!tracemalloc_config.tracing) {

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp