From 51df4369960a4a1d1d5fd9c295636106fd7e886f Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Thu, 11 Sep 2025 15:54:10 +0100 Subject: [PATCH 1/2] gh-138794: Communicate to PyRefTracer when they are being replaced --- Doc/c-api/init.rst | 16 +++++++++ Include/cpython/object.h | 1 + ...-09-11-15-56-18.gh-issue-138794.nrOn1K.rst | 5 +++ Modules/_testcapimodule.c | 35 ++++++++++++++----- Objects/object.c | 6 ++++ 5 files changed, 54 insertions(+), 9 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-09-11-15-56-18.gh-issue-138794.nrOn1K.rst diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index bb9e08acee14c2..855aec6d725a79 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -2011,6 +2011,11 @@ Reference tracing is set to :c:data:`PyRefTracer_DESTROY`). The **data** argument is the opaque pointer that was provided when :c:func:`PyRefTracer_SetTracer` was called. + If a new tracing function is registered replacing the current a call to the + trace function will be made with the object set to **NULL** and **event** set to + :c:data:`PyRefTracer_TRACKER_REMOVED`. This will happen just before the new + function is registered. + .. versionadded:: 3.13 .. c:var:: int PyRefTracer_CREATE @@ -2023,6 +2028,13 @@ Reference tracing The value for the *event* parameter to :c:type:`PyRefTracer` functions when a Python object has been destroyed. +.. c:var:: int PyRefTracer_TRACKER_REMOVED + + The value for the *event* parameter to :c:type:`PyRefTracer` functions when the + current tracer is about to be replaced by a new one. + + .. versionadded:: 3.14 + .. c:function:: int PyRefTracer_SetTracer(PyRefTracer tracer, void *data) Register a reference tracer function. The function will be called when a new @@ -2038,6 +2050,10 @@ Reference tracing There must be an :term:`attached thread state` when calling this function. + If another tracer function was already registered, the old function will be + called with **event** set to :c:data:`PyRefTracer_TRACKER_REMOVED` just before + the new function is registered. + .. versionadded:: 3.13 .. c:function:: PyRefTracer PyRefTracer_GetTracer(void** data) diff --git a/Include/cpython/object.h b/Include/cpython/object.h index b244c062c7679e..4e6f86f29d8473 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -463,6 +463,7 @@ PyAPI_FUNC(int) PyUnstable_Type_AssignVersionTag(PyTypeObject *type); typedef enum { PyRefTracer_CREATE = 0, PyRefTracer_DESTROY = 1, + PyRefTracer_TRACKER_REMOVED = 2, } PyRefTracerEvent; typedef int (*PyRefTracer)(PyObject *, PyRefTracerEvent event, void *); diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-09-11-15-56-18.gh-issue-138794.nrOn1K.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-09-11-15-56-18.gh-issue-138794.nrOn1K.rst new file mode 100644 index 00000000000000..78279d2f3db1b9 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-09-11-15-56-18.gh-issue-138794.nrOn1K.rst @@ -0,0 +1,5 @@ +When a new tracing function is registered with +:c:func:`PyRefTracer_SetTracerC`, replacing the current a call to the trace +function will be made with the object set to **NULL** and **event** set to +:c:data:`PyRefTracer_TRACKER_REMOVED`. This will happen just before the new +function is registered. Patch by Pablo Galindo diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 4f22a70802009a..c80a780e22ca34 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2319,6 +2319,7 @@ test_weakref_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) struct simpletracer_data { int create_count; int destroy_count; + int tracker_removed; void* addresses[10]; }; @@ -2326,10 +2327,18 @@ 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++; + switch (event) { + case PyRefTracer_CREATE: + the_data->create_count++; + break; + case PyRefTracer_DESTROY: + the_data->destroy_count++; + break; + case PyRefTracer_TRACKER_REMOVED: + the_data->tracker_removed++; + break; + default: + return -1; } return 0; } @@ -2393,6 +2402,10 @@ test_reftracer(PyObject *ob, PyObject *Py_UNUSED(ignored)) PyErr_SetString(PyExc_ValueError, "The object destruction was not correctly traced"); goto failed; } + if (tracer_data.tracker_removed != 1) { + PyErr_SetString(PyExc_ValueError, "The tracker removal was not correctly traced"); + goto failed; + } PyRefTracer_SetTracer(current_tracer, current_data); Py_RETURN_NONE; failed: @@ -2533,11 +2546,15 @@ code_offset_to_line(PyObject* self, PyObject* const* args, Py_ssize_t nargsf) static int _reftrace_printer(PyObject *obj, PyRefTracerEvent event, void *counter_data) { - if (event == PyRefTracer_CREATE) { - printf("CREATE %s\n", Py_TYPE(obj)->tp_name); - } - else { // PyRefTracer_DESTROY - printf("DESTROY %s\n", Py_TYPE(obj)->tp_name); + switch (event) { + case PyRefTracer_CREATE: + printf("CREATE %s\n", Py_TYPE(obj)->tp_name); + break; + case PyRefTracer_DESTROY: + printf("DESTROY %s\n", Py_TYPE(obj)->tp_name); + break; + case PyRefTracer_TRACKER_REMOVED: + return 0; } return 0; } diff --git a/Objects/object.c b/Objects/object.c index bd3ba02f8eb255..a1c19b13a40c68 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -3286,6 +3286,12 @@ _Py_SetRefcnt(PyObject *ob, Py_ssize_t refcnt) int PyRefTracer_SetTracer(PyRefTracer tracer, void *data) { _Py_AssertHoldsTstate(); + if (_PyRuntime.ref_tracer.tracer_func != NULL) { + _PyReftracerTrack(NULL, PyRefTracer_TRACKER_REMOVED); + if (PyErr_Occurred()) { + return -1; + } + } _PyRuntime.ref_tracer.tracer_func = tracer; _PyRuntime.ref_tracer.tracer_data = data; return 0; From cba879b27281926b231376034736cae49151bbae Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Thu, 11 Sep 2025 16:55:49 +0100 Subject: [PATCH 2/2] Update Misc/NEWS.d/next/Core_and_Builtins/2025-09-11-15-56-18.gh-issue-138794.nrOn1K.rst Co-authored-by: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> --- .../2025-09-11-15-56-18.gh-issue-138794.nrOn1K.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-09-11-15-56-18.gh-issue-138794.nrOn1K.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-09-11-15-56-18.gh-issue-138794.nrOn1K.rst index 78279d2f3db1b9..2fb0f07329899f 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2025-09-11-15-56-18.gh-issue-138794.nrOn1K.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-09-11-15-56-18.gh-issue-138794.nrOn1K.rst @@ -1,5 +1,5 @@ When a new tracing function is registered with -:c:func:`PyRefTracer_SetTracerC`, replacing the current a call to the trace +:c:func:`PyRefTracer_SetTracer`, replacing the current a call to the trace function will be made with the object set to **NULL** and **event** set to :c:data:`PyRefTracer_TRACKER_REMOVED`. This will happen just before the new function is registered. Patch by Pablo Galindo