https://github.com/python/cpython/commit/b0a43d4ec627f904e59fd4d2b7207bec01ffe25d
commit: b0a43d4ec627f904e59fd4d2b7207bec01ffe25d
branch: 3.15
author: Neil Schemenauer <[email protected]>
committer: hugovk <[email protected]>
date: 2026-08-25T08:24:31+09:00
summary:
[3.15] gh-155978: Fix leak in update_slot_after_setattr() (GH-155979) (#155984)
(cherry picked from commit f381d1634c1eddedddcb8ea5d4ae5a4dd7564822)
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 497c94170ae1387..6dac77f68829661 100644
--- a/Lib/test/test_free_threading/test_type.py
+++ b/Lib/test/test_free_threading/test_type.py
@@ -181,6 +181,27 @@ 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 b648b60cfc89dc5..0d3f98ff446aacb 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -6760,24 +6760,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]