Skip to content

Commit f01181b

Browse files
authored
gh-138794: Communicate to PyRefTracer when they are being replaced (#138797)
1 parent baf7470 commit f01181b

File tree

5 files changed

+54
-9
lines changed

5 files changed

+54
-9
lines changed

Doc/c-api/init.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2010,6 +2010,11 @@ Reference tracing
20102010
is set to :c:data:`PyRefTracer_DESTROY`). The **data** argument is the opaque pointer
20112011
that was provided when :c:func:`PyRefTracer_SetTracer` was called.
20122012
2013+
If a new tracing function is registered replacing the current a call to the
2014+
trace function will be made with the object set to **NULL** and **event** set to
2015+
:c:data:`PyRefTracer_TRACKER_REMOVED`. This will happen just before the new
2016+
function is registered.
2017+
20132018
.. versionadded:: 3.13
20142019
20152020
.. c:var:: int PyRefTracer_CREATE
@@ -2022,6 +2027,13 @@ Reference tracing
20222027
The value for the *event* parameter to :c:type:`PyRefTracer` functions when a Python
20232028
object has been destroyed.
20242029
2030+
.. c:var:: int PyRefTracer_TRACKER_REMOVED
2031+
2032+
The value for the *event* parameter to :c:type:`PyRefTracer` functions when the
2033+
current tracer is about to be replaced by a new one.
2034+
2035+
.. versionadded:: 3.14
2036+
20252037
.. c:function:: int PyRefTracer_SetTracer(PyRefTracer tracer, void *data)
20262038
20272039
Register a reference tracer function. The function will be called when a new
@@ -2037,6 +2049,10 @@ Reference tracing
20372049
20382050
There must be an :term:`attached thread state` when calling this function.
20392051
2052+
If another tracer function was already registered, the old function will be
2053+
called with **event** set to :c:data:`PyRefTracer_TRACKER_REMOVED` just before
2054+
the new function is registered.
2055+
20402056
.. versionadded:: 3.13
20412057
20422058
.. c:function:: PyRefTracer PyRefTracer_GetTracer(void** data)

Include/cpython/object.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,7 @@ PyAPI_FUNC(int) PyUnstable_Type_AssignVersionTag(PyTypeObject *type);
463463
typedef enum {
464464
PyRefTracer_CREATE = 0,
465465
PyRefTracer_DESTROY = 1,
466+
PyRefTracer_TRACKER_REMOVED = 2,
466467
} PyRefTracerEvent;
467468

468469
typedef int (*PyRefTracer)(PyObject *, PyRefTracerEvent event, void *);
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
When a new tracing function is registered with
2+
:c:func:`PyRefTracer_SetTracer`, replacing the current a call to the trace
3+
function will be made with the object set to **NULL** and **event** set to
4+
:c:data:`PyRefTracer_TRACKER_REMOVED`. This will happen just before the new
5+
function is registered. Patch by Pablo Galindo

Modules/_testcapimodule.c

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2319,17 +2319,26 @@ test_weakref_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
23192319
struct simpletracer_data {
23202320
int create_count;
23212321
int destroy_count;
2322+
int tracker_removed;
23222323
void* addresses[10];
23232324
};
23242325

23252326
static int _simpletracer(PyObject *obj, PyRefTracerEvent event, void* data) {
23262327
struct simpletracer_data* the_data = (struct simpletracer_data*)data;
23272328
assert(the_data->create_count + the_data->destroy_count < (int)Py_ARRAY_LENGTH(the_data->addresses));
23282329
the_data->addresses[the_data->create_count + the_data->destroy_count] = obj;
2329-
if (event == PyRefTracer_CREATE) {
2330-
the_data->create_count++;
2331-
} else {
2332-
the_data->destroy_count++;
2330+
switch (event) {
2331+
case PyRefTracer_CREATE:
2332+
the_data->create_count++;
2333+
break;
2334+
case PyRefTracer_DESTROY:
2335+
the_data->destroy_count++;
2336+
break;
2337+
case PyRefTracer_TRACKER_REMOVED:
2338+
the_data->tracker_removed++;
2339+
break;
2340+
default:
2341+
return -1;
23332342
}
23342343
return 0;
23352344
}
@@ -2393,6 +2402,10 @@ test_reftracer(PyObject *ob, PyObject *Py_UNUSED(ignored))
23932402
PyErr_SetString(PyExc_ValueError, "The object destruction was not correctly traced");
23942403
goto failed;
23952404
}
2405+
if (tracer_data.tracker_removed != 1) {
2406+
PyErr_SetString(PyExc_ValueError, "The tracker removal was not correctly traced");
2407+
goto failed;
2408+
}
23962409
PyRefTracer_SetTracer(current_tracer, current_data);
23972410
Py_RETURN_NONE;
23982411
failed:
@@ -2533,11 +2546,15 @@ code_offset_to_line(PyObject* self, PyObject* const* args, Py_ssize_t nargsf)
25332546
static int
25342547
_reftrace_printer(PyObject *obj, PyRefTracerEvent event, void *counter_data)
25352548
{
2536-
if (event == PyRefTracer_CREATE) {
2537-
printf("CREATE %s\n", Py_TYPE(obj)->tp_name);
2538-
}
2539-
else { // PyRefTracer_DESTROY
2540-
printf("DESTROY %s\n", Py_TYPE(obj)->tp_name);
2549+
switch (event) {
2550+
case PyRefTracer_CREATE:
2551+
printf("CREATE %s\n", Py_TYPE(obj)->tp_name);
2552+
break;
2553+
case PyRefTracer_DESTROY:
2554+
printf("DESTROY %s\n", Py_TYPE(obj)->tp_name);
2555+
break;
2556+
case PyRefTracer_TRACKER_REMOVED:
2557+
return 0;
25412558
}
25422559
return 0;
25432560
}

Objects/object.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3287,6 +3287,12 @@ _Py_SetRefcnt(PyObject *ob, Py_ssize_t refcnt)
32873287

32883288
int PyRefTracer_SetTracer(PyRefTracer tracer, void *data) {
32893289
_Py_AssertHoldsTstate();
3290+
if (_PyRuntime.ref_tracer.tracer_func != NULL) {
3291+
_PyReftracerTrack(NULL, PyRefTracer_TRACKER_REMOVED);
3292+
if (PyErr_Occurred()) {
3293+
return -1;
3294+
}
3295+
}
32903296
_PyRuntime.ref_tracer.tracer_func = tracer;
32913297
_PyRuntime.ref_tracer.tracer_data = data;
32923298
return 0;

0 commit comments

Comments
 (0)