https://github.com/python/cpython/commit/f01181b595971fd1af12cf43ab6731a4e5766142
commit: f01181b595971fd1af12cf43ab6731a4e5766142
branch: main
author: Pablo Galindo Salgado <[email protected]>
committer: pablogsal <[email protected]>
date: 2025-09-15T11:12:09+01:00
summary:

gh-138794: Communicate to PyRefTracer when they are being replaced (#138797)

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2025-09-11-15-56-18.gh-issue-138794.nrOn1K.rst
M Doc/c-api/init.rst
M Include/cpython/object.h
M Modules/_testcapimodule.c
M Objects/object.c

diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst
index 379330f380400f..199b64387266bf 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 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..2fb0f07329899f
--- /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 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 aaa3c0b338434e..2c07c2e9841b0d 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;

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to