[3.15] gh-155978: Fix leak in update_slot_after_setattr() (GH-155979) (#155984)
hugovk <[email protected]>
| Newsgroups | gmane.comp.python.cvs |
|---|---|
| Message-ID | <[email protected]> |
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]