diff --git a/Misc/NEWS.d/next/C_API/2026-08-25-12-00-00.gh-issue-131740.Ft7Rz2.rst b/Misc/NEWS.d/next/C_API/2026-08-25-12-00-00.gh-issue-131740.Ft7Rz2.rst new file mode 100644 index 000000000000000..83701b66ca85f85 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2026-08-25-12-00-00.gh-issue-131740.Ft7Rz2.rst @@ -0,0 +1,3 @@ +On the free-threaded build, :c:func:`PyUnstable_GC_VisitObjects` now also +visits frozen objects (objects moved to the permanent generation by +:func:`gc.freeze`), matching the behavior of the default build. diff --git a/Modules/_testcapi/gc.c b/Modules/_testcapi/gc.c index 863cb52980f9425..dac7066565f7ffc 100644 --- a/Modules/_testcapi/gc.c +++ b/Modules/_testcapi/gc.c @@ -195,6 +195,57 @@ test_gc_visit_objects_basic(PyObject *Py_UNUSED(self), Py_RETURN_NONE; } +static int +gc_call_no_args(const char *method) +{ + PyObject *gc = PyImport_ImportModule("gc"); + if (gc == NULL) { + return -1; + } + PyObject *res = PyObject_CallMethod(gc, method, NULL); + Py_DECREF(gc); + if (res == NULL) { + return -1; + } + Py_DECREF(res); + return 0; +} + +// gh-131740: frozen objects must be visited too. +static PyObject * +test_gc_visit_objects_frozen(PyObject *Py_UNUSED(self), + PyObject *Py_UNUSED(ignored)) +{ + PyObject *obj; + struct gc_visit_state_basic state; + + obj = PyList_New(0); + if (obj == NULL) { + return NULL; + } + if (gc_call_no_args("freeze") < 0) { + Py_DECREF(obj); + return NULL; + } + state.target = obj; + state.found = 0; + + PyUnstable_GC_VisitObjects(gc_visit_callback_basic, &state); + + int err = gc_call_no_args("unfreeze"); + Py_DECREF(obj); + if (err < 0) { + return NULL; + } + if (!state.found) { + PyErr_SetString( + PyExc_AssertionError, + "test_gc_visit_objects_frozen: Didn't find frozen list"); + return NULL; + } + Py_RETURN_NONE; +} + static int gc_visit_callback_exit_early(PyObject *obj, void *arg) { @@ -316,6 +367,7 @@ static PyType_Spec ObjExtraData_TypeSpec = { static PyMethodDef test_methods[] = { {"test_gc_control", test_gc_control, METH_NOARGS}, {"test_gc_visit_objects_basic", test_gc_visit_objects_basic, METH_NOARGS, NULL}, + {"test_gc_visit_objects_frozen", test_gc_visit_objects_frozen, METH_NOARGS, NULL}, {"test_gc_visit_objects_exit_early", test_gc_visit_objects_exit_early, METH_NOARGS, NULL}, {"without_gc", without_gc, METH_O, NULL}, {"with_tp_del", with_tp_del, METH_VARARGS, NULL}, diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index fbd13d1e4d87f25..f865ac2f7db0853 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -2860,7 +2860,7 @@ static bool custom_visitor_wrapper(const mi_heap_t *heap, const mi_heap_area_t *area, void *block, size_t block_size, void *args) { - PyObject *op = op_from_block(block, args, false); + PyObject *op = op_from_block(block, args, true); if (op == NULL) { return true; }