https://github.com/python/cpython/commit/f381d1634c1eddedddcb8ea5d4ae5a4dd7564822
commit: f381d1634c1eddedddcb8ea5d4ae5a4dd7564822
branch: main
author: Neil Schemenauer <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-08-18T07:55:08+05:30
summary:
gh-155978: Fix leak in update_slot_after_setattr() (#155979)
files:
A
Misc/NEWS.d/next/Core_and_Builtins/2026-08-17-15-13-14.gh-issue-155978.4ztALD.rst
M Lib/test/test_free_threading/test_type.py
M Objects/typeobject.c
diff --git a/Lib/test/test_free_threading/test_type.py
b/Lib/test/test_free_threading/test_type.py
index d64a3f38f148305..a55c4815a038376 100644
--- a/Lib/test/test_free_threading/test_type.py
+++ b/Lib/test/test_free_threading/test_type.py
@@ -324,6 +324,26 @@ def wrapper():
for reader in readers:
reader.join()
+ def test_setattr_many_subclasses(self):
+ # gh-155978: Updating a special method queues a slot update for every
+ # affected subclass. Keep enough subclasses alive to require
+ # heap-allocated queue chunks in addition to the stack chunk.
+ class Base:
+ pass
+
+ subclasses = [type(f"Sub{i}", (Base,), {}) for i in range(100)]
+
+ def custom_repr(self):
+ return "custom repr"
+
+ Base.__repr__ = custom_repr
+ self.assertTrue(all(repr(cls()) == "custom repr"
+ for cls in subclasses))
+
+ del Base.__repr__
+ self.assertTrue(all(repr(cls()) != "custom repr"
+ for cls in subclasses))
+
def test_concurrent_setattr_deadlock(self):
# gh-155400: two threads assigning to a special method of the same
# class could deadlock. One thread held the type lock and waited for
diff --git
a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-17-15-13-14.gh-issue-155978.4ztALD.rst
b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-17-15-13-14.gh-issue-155978.4ztALD.rst
new file mode 100644
index 000000000000000..8b807d763d604a2
--- /dev/null
+++
b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-17-15-13-14.gh-issue-155978.4ztALD.rst
@@ -0,0 +1,2 @@
+Fix a memory leak in the free-threaded build when setting or deleting a
+special method (such as ``__repr__``) on a class that has many subclasses.
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index 420eb855a35d16c..572e302df8d80d5 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -6598,24 +6598,29 @@ static int
update_slot_after_setattr(PyTypeObject *type, PyObject *name)
{
#ifdef Py_GIL_DISABLED
- // stack allocate one chunk since that's all we need
assert(SLOT_UPDATE_CHUNK_SIZE >= MAX_EQUIV);
slot_update_chunk_t chunk = {0};
+ // Stack allocate the first chunk. It is usually the only one needed but
+ // updates are queued for subclasses as well, so more chunks are needed if
+ // the type has more than SLOT_UPDATE_CHUNK_SIZE subclasses.
slot_update_t queued_updates = {&chunk};
- if (update_slot(type, name, &queued_updates) < 0) {
- return -1;
- }
- if (queued_updates.head->n > 0) {
+ int res = update_slot(type, name, &queued_updates);
+ if (res == 0 && queued_updates.head->n > 0) {
apply_type_slot_updates(&queued_updates);
ASSERT_TYPE_LOCK_HELD();
- // should never allocate another chunk
- assert(chunk.prev == NULL);
}
+ slot_update_chunk_t *cur = queued_updates.head;
+ while (cur != &chunk) {
+ slot_update_chunk_t *prev = cur->prev;
+ PyMem_Free(cur);
+ cur = prev;
+ }
+ return res;
#else
update_slot(type, name, NULL);
-#endif
return 0;
+#endif
}
static int
_______________________________________________
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]