File tree
Lib/test/test_free_threading
Misc/NEWS.d/next/Core_and_Builtins
Objects
Original file line numberDiff line numberDiff line change@@ -324,6 +324,26 @@ def wrapper():
324324forreaderinreaders:
325325reader.join()
326326327+deftest_setattr_many_subclasses(self):
328+# gh-155978: Updating a special method queues a slot update for every
329+# affected subclass. Keep enough subclasses alive to require
330+# heap-allocated queue chunks in addition to the stack chunk.
331+classBase:
332+pass
333+334+subclasses= [type(f"Sub{i}", (Base,), {}) foriinrange(100)]
335+336+defcustom_repr(self):
337+return"custom repr"
338+339+Base.__repr__=custom_repr
340+self.assertTrue(all(repr(cls()) =="custom repr"
341+forclsinsubclasses))
342+343+delBase.__repr__
344+self.assertTrue(all(repr(cls()) !="custom repr"
345+forclsinsubclasses))
346+327347deftest_concurrent_setattr_deadlock(self):
328348# gh-155400: two threads assigning to a special method of the same
329349# class could deadlock. One thread held the type lock and waited for
Original file line numberDiff line numberDiff line change@@ -0,0 +1,2 @@
1+Fix a memory leak in the free-threaded build when setting or deleting a
2+special method (such as ``__repr__``) on a class that has many subclasses.
Original file line numberDiff line numberDiff line change@@ -6598,24 +6598,29 @@ static int
65986598update_slot_after_setattr(PyTypeObject*type, PyObject*name)
65996599{
66006600#ifdefPy_GIL_DISABLED
6601-// stack allocate one chunk since that's all we need
66026601assert(SLOT_UPDATE_CHUNK_SIZE >= MAX_EQUIV);
66036602slot_update_chunk_tchunk= {0};
6603+// Stack allocate the first chunk. It is usually the only one needed but
6604+// updates are queued for subclasses as well, so more chunks are needed if
6605+// the type has more than SLOT_UPDATE_CHUNK_SIZE subclasses.
66046606slot_update_tqueued_updates= {&chunk};
660566076606-if (update_slot(type, name, &queued_updates) <0) {
6607-return-1;
6608- }
6609-if (queued_updates.head->n>0) {
6608+intres=update_slot(type, name, &queued_updates);
6609+if (res==0&&queued_updates.head->n>0) {
66106610apply_type_slot_updates(&queued_updates);
66116611ASSERT_TYPE_LOCK_HELD();
6612-// should never allocate another chunk
6613-assert(chunk.prev==NULL);
66146612 }
6613+slot_update_chunk_t*cur=queued_updates.head;
6614+while (cur!=&chunk) {
6615+slot_update_chunk_t*prev=cur->prev;
6616+PyMem_Free(cur);
6617+cur=prev;
6618+ }
6619+returnres;
66156620#else
66166621update_slot(type, name, NULL);
6617-#endif
66186622return0;
6623+#endif
66196624}
6620662566216626staticint