From: Pablo Galindo Salgado Date: Mon, 15 Sep 2025 10:12:09 +0000 (+0100) Subject: gh-138794: Communicate to PyRefTracer when they are being replaced (#138797) X-Git-Tag: v3.15.0a1~373 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=f01181b595971fd1af12cf43ab6731a4e5766142;p=thirdparty%2FPython%2Fcpython.git gh-138794: Communicate to PyRefTracer when they are being replaced (#138797) --- diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index 379330f38040..199b64387266 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -2010,6 +2010,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 @@ -2022,6 +2027,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 @@ -2037,6 +2049,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 b244c062c767..4e6f86f29d84 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 000000000000..2fb0f0732989 --- /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_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 diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 4f22a7080200..c80a780e22ca 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 aaa3c0b33843..2c07c2e9841b 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -3287,6 +3287,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;