diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-26-22-03-16.gh-issue-152235.ZKWiWk.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-26-22-03-16.gh-issue-152235.ZKWiWk.rst new file mode 100644 index 000000000000000..bcc128404897da1 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-26-22-03-16.gh-issue-152235.ZKWiWk.rst @@ -0,0 +1,2 @@ +Defer GC tracking of :meth:`set.intersection`, :meth:`set.difference` and +:meth:`set.symmetric_difference`. Patch by Donghee Na. diff --git a/Objects/setobject.c b/Objects/setobject.c index 0c63ef7fb272c50..aec73993451f654 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -1345,14 +1345,14 @@ set_update_impl(PySetObject *so, PyObject * const *others, can be retrieved or updated in a single cache line. */ +// Build a set/frozenset left GC-untracked; the caller must _PyObject_GC_TRACK() +// it once fully built, so a half-built set is never exposed during filling. static PyObject * -make_new_set(PyTypeObject *type, PyObject *iterable) +make_new_set_untracked(PyTypeObject *type, PyObject *iterable) { assert(PyType_Check(type)); PySetObject *so; - // Allocate untracked: the fill below runs user code, and a half-built - // set must not be reachable from another thread via gc.get_objects(). so = (PySetObject *)_PyType_AllocNoTrack(type, 0); if (so == NULL) return NULL; @@ -1372,13 +1372,21 @@ make_new_set(PyTypeObject *type, PyObject *iterable) } } - // Track only once fully built. - _PyObject_GC_TRACK(so); return (PyObject *)so; } static PyObject * -make_new_set_basetype(PyTypeObject *type, PyObject *iterable) +make_new_set(PyTypeObject *type, PyObject *iterable) +{ + PyObject *so = make_new_set_untracked(type, iterable); + if (so != NULL) { + _PyObject_GC_TRACK(so); + } + return so; +} + +static PyObject * +make_new_set_basetype_untracked(PyTypeObject *type, PyObject *iterable) { if (type != &PySet_Type && type != &PyFrozenSet_Type) { if (PyType_IsSubtype(type, &PySet_Type)) @@ -1386,7 +1394,17 @@ make_new_set_basetype(PyTypeObject *type, PyObject *iterable) else type = &PyFrozenSet_Type; } - return make_new_set(type, iterable); + return make_new_set_untracked(type, iterable); +} + +static PyObject * +make_new_set_basetype(PyTypeObject *type, PyObject *iterable) +{ + PyObject *so = make_new_set_basetype_untracked(type, iterable); + if (so != NULL) { + _PyObject_GC_TRACK(so); + } + return so; } // gh-140232: check whether a frozenset can be untracked from the GC @@ -1686,7 +1704,7 @@ set_intersection(PySetObject *so, PyObject *other) if ((PyObject *)so == other) return set_copy_impl(so); - result = (PySetObject *)make_new_set_basetype(Py_TYPE(so), NULL); + result = (PySetObject *)make_new_set_basetype_untracked(Py_TYPE(so), NULL); if (result == NULL) return NULL; @@ -1719,6 +1737,7 @@ set_intersection(PySetObject *so, PyObject *other) } Py_DECREF(key); } + _PyObject_GC_TRACK(result); return (PyObject *)result; } @@ -1750,6 +1769,7 @@ set_intersection(PySetObject *so, PyObject *other) Py_DECREF(result); return NULL; } + _PyObject_GC_TRACK(result); return (PyObject *)result; error: Py_DECREF(it); @@ -2064,7 +2084,7 @@ set_difference(PySetObject *so, PyObject *other) return set_copy_and_difference(so, other); } - result = make_new_set_basetype(Py_TYPE(so), NULL); + result = make_new_set_basetype_untracked(Py_TYPE(so), NULL); if (result == NULL) return NULL; @@ -2088,6 +2108,7 @@ set_difference(PySetObject *so, PyObject *other) } Py_DECREF(key); } + _PyObject_GC_TRACK(result); return result; } @@ -2111,6 +2132,7 @@ set_difference(PySetObject *so, PyObject *other) } Py_DECREF(key); } + _PyObject_GC_TRACK(result); return result; } @@ -2312,7 +2334,8 @@ static PyObject * set_symmetric_difference_impl(PySetObject *so, PyObject *other) /*[clinic end generated code: output=270ee0b5d42b0797 input=8c29b0be90d47feb]*/ { - PySetObject *result = (PySetObject *)make_new_set_basetype(Py_TYPE(so), NULL); + PySetObject *result = + (PySetObject *)make_new_set_basetype_untracked(Py_TYPE(so), NULL); if (result == NULL) { return NULL; } @@ -2324,6 +2347,7 @@ set_symmetric_difference_impl(PySetObject *so, PyObject *other) Py_DECREF(result); return NULL; } + _PyObject_GC_TRACK(result); return (PyObject *)result; }