Skip to content

Commit c6ddd69

Browse files
committed
Fix test_c_subclass_of_heap_ctype_with_del_modifying_dunder_class_only_decrefs_once
Refactor the test so that the specialized and unspecialized implementation of loading the argument to sys.getrefcount use the same refcounting approach. Previously, the argument would be evaluated by loading an attribute from a module. This specializes to LOAD_ATTR_MODULE. In free-threaded builds the unspecialized form of LOAD_ATTR always creates a new reference for its result, while the specialized form does not create a reference if the result uses deferred refcounting. This causes a difference in the result returned from sys.getrefcount, depending on whether or not the bytecode has been specialized (e.g. on runs > 1 in refleak tests). The refactored version uses LOAD_GLOBAL, whose specialized and unspecialized forms both do not create references when the result uses deferred refcounting. Also refactor the test to handle the difference in the result returned from sys.getrefcount in default builds (includes the temporary reference on the operand stack) and free-threaded builds (no temporary reference is created for deferred values).
1 parent 38140d9 commit c6ddd69

File tree

1 file changed

+38
-9
lines changed

1 file changed

+38
-9
lines changed

Lib/test/test_capi/test_misc.py

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
# Skip this test if the _testcapi module isn't available.
4949
_testcapi = import_helper.import_module('_testcapi')
5050

51+
from _testcapi import HeapCTypeSubclass, HeapCTypeSubclassWithFinalizer
52+
5153
import _testlimitedcapi
5254
import _testinternalcapi
5355

@@ -646,9 +648,9 @@ def test_c_subclass_of_heap_ctype_with_tpdealloc_decrefs_once(self):
646648
self.assertEqual(type_refcnt - 1, sys.getrefcount(_testcapi.HeapCTypeSubclass))
647649

648650
def test_c_subclass_of_heap_ctype_with_del_modifying_dunder_class_only_decrefs_once(self):
649-
subclass_instance = _testcapi.HeapCTypeSubclassWithFinalizer()
650-
type_refcnt = sys.getrefcount(_testcapi.HeapCTypeSubclassWithFinalizer)
651-
new_type_refcnt = sys.getrefcount(_testcapi.HeapCTypeSubclass)
651+
subclass_instance = HeapCTypeSubclassWithFinalizer()
652+
type_refcnt = sys.getrefcount(HeapCTypeSubclassWithFinalizer)
653+
new_type_refcnt = sys.getrefcount(HeapCTypeSubclass)
652654

653655
# Test that subclass instance was fully created
654656
self.assertEqual(subclass_instance.value, 10)
@@ -658,19 +660,46 @@ def test_c_subclass_of_heap_ctype_with_del_modifying_dunder_class_only_decrefs_o
658660
del subclass_instance
659661

660662
# Test that setting __class__ modified the reference counts of the types
663+
#
664+
# This is highly sensitive to implementation details and may break in the future.
665+
#
666+
# We expect the refcount on the old type, HeapCTypeSubclassWithFinalizer, to
667+
# remain the same: the finalizer gets a strong reference (+1) when it gets the
668+
# type from the module and setting __class__ decrements the refcount (-1).
669+
#
670+
# We expect the refcount on the new type, HeapCTypeSubclass, to increase by 2:
671+
# the finalizer get a strong reference (+1) when it gets the type from the
672+
# module and setting __class__ increments the refcount (+1).
673+
expected_type_refcnt = type_refcnt
674+
expected_new_type_refcnt = new_type_refcnt + 2
675+
676+
if not Py_GIL_DISABLED:
677+
# In default builds the result returned from sys.getrefcount
678+
# includes a temporary reference that is created by the interpreter
679+
# when it pushes its argument on the operand stack. This temporary
680+
# reference is not included in the result returned by Py_REFCNT, which
681+
# is used in the finalizer.
682+
#
683+
# In free-threaded builds the result returned from sys.getrefcount
684+
# does not include the temporary reference. Types use deferred
685+
# refcounting and the interpreter will not create a new reference
686+
# for deferred values on the operand stack.
687+
expected_type_refcnt -= 1
688+
expected_new_type_refcnt -= 1
689+
661690
if support.Py_DEBUG:
662691
# gh-89373: In debug mode, _Py_Dealloc() keeps a strong reference
663692
# to the type while calling tp_dealloc()
664-
self.assertEqual(type_refcnt, _testcapi.HeapCTypeSubclassWithFinalizer.refcnt_in_del)
665-
else:
666-
self.assertEqual(type_refcnt - 1, _testcapi.HeapCTypeSubclassWithFinalizer.refcnt_in_del)
667-
self.assertEqual(new_type_refcnt + 1, _testcapi.HeapCTypeSubclass.refcnt_in_del)
693+
expected_type_refcnt += 1
694+
695+
self.assertEqual(expected_type_refcnt, HeapCTypeSubclassWithFinalizer.refcnt_in_del)
696+
self.assertEqual(expected_new_type_refcnt, HeapCTypeSubclass.refcnt_in_del)
668697

669698
# Test that the original type already has decreased its refcnt
670-
self.assertEqual(type_refcnt - 1, sys.getrefcount(_testcapi.HeapCTypeSubclassWithFinalizer))
699+
self.assertEqual(type_refcnt - 1, sys.getrefcount(HeapCTypeSubclassWithFinalizer))
671700

672701
# Test that subtype_dealloc decref the newly assigned __class__ only once
673-
self.assertEqual(new_type_refcnt, sys.getrefcount(_testcapi.HeapCTypeSubclass))
702+
self.assertEqual(new_type_refcnt, sys.getrefcount(HeapCTypeSubclass))
674703

675704
def test_heaptype_with_setattro(self):
676705
obj = _testcapi.HeapCTypeSetattr()

0 commit comments

Comments
 (0)