gh-155978: Fix leak in update_slot_after_setattr() (#155979) · python/cpython@f381d16

GitHub

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