Skip to content

Commit

Permalink
Fix test_c_subclass_of_heap_ctype_with_del_modifying_dunder_class_onl…
Browse files Browse the repository at this point in the history
…y_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).
  • Loading branch information
mpage committed Dec 9, 2024
1 parent 38140d9 commit c6ddd69
Showing 1 changed file with 38 additions and 9 deletions.
47 changes: 38 additions & 9 deletions Lib/test/test_capi/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
# Skip this test if the _testcapi module isn't available.
_testcapi = import_helper.import_module('_testcapi')

from _testcapi import HeapCTypeSubclass, HeapCTypeSubclassWithFinalizer

import _testlimitedcapi
import _testinternalcapi

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

def test_c_subclass_of_heap_ctype_with_del_modifying_dunder_class_only_decrefs_once(self):
subclass_instance = _testcapi.HeapCTypeSubclassWithFinalizer()
type_refcnt = sys.getrefcount(_testcapi.HeapCTypeSubclassWithFinalizer)
new_type_refcnt = sys.getrefcount(_testcapi.HeapCTypeSubclass)
subclass_instance = HeapCTypeSubclassWithFinalizer()
type_refcnt = sys.getrefcount(HeapCTypeSubclassWithFinalizer)
new_type_refcnt = sys.getrefcount(HeapCTypeSubclass)

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

# Test that setting __class__ modified the reference counts of the types
#
# This is highly sensitive to implementation details and may break in the future.
#
# We expect the refcount on the old type, HeapCTypeSubclassWithFinalizer, to
# remain the same: the finalizer gets a strong reference (+1) when it gets the
# type from the module and setting __class__ decrements the refcount (-1).
#
# We expect the refcount on the new type, HeapCTypeSubclass, to increase by 2:
# the finalizer get a strong reference (+1) when it gets the type from the
# module and setting __class__ increments the refcount (+1).
expected_type_refcnt = type_refcnt
expected_new_type_refcnt = new_type_refcnt + 2

if not Py_GIL_DISABLED:
# In default builds the result returned from sys.getrefcount
# includes a temporary reference that is created by the interpreter
# when it pushes its argument on the operand stack. This temporary
# reference is not included in the result returned by Py_REFCNT, which
# is used in the finalizer.
#
# In free-threaded builds the result returned from sys.getrefcount
# does not include the temporary reference. Types use deferred
# refcounting and the interpreter will not create a new reference
# for deferred values on the operand stack.
expected_type_refcnt -= 1
expected_new_type_refcnt -= 1

if support.Py_DEBUG:
# gh-89373: In debug mode, _Py_Dealloc() keeps a strong reference
# to the type while calling tp_dealloc()
self.assertEqual(type_refcnt, _testcapi.HeapCTypeSubclassWithFinalizer.refcnt_in_del)
else:
self.assertEqual(type_refcnt - 1, _testcapi.HeapCTypeSubclassWithFinalizer.refcnt_in_del)
self.assertEqual(new_type_refcnt + 1, _testcapi.HeapCTypeSubclass.refcnt_in_del)
expected_type_refcnt += 1

self.assertEqual(expected_type_refcnt, HeapCTypeSubclassWithFinalizer.refcnt_in_del)
self.assertEqual(expected_new_type_refcnt, HeapCTypeSubclass.refcnt_in_del)

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

# Test that subtype_dealloc decref the newly assigned __class__ only once
self.assertEqual(new_type_refcnt, sys.getrefcount(_testcapi.HeapCTypeSubclass))
self.assertEqual(new_type_refcnt, sys.getrefcount(HeapCTypeSubclass))

def test_heaptype_with_setattro(self):
obj = _testcapi.HeapCTypeSetattr()
Expand Down

0 comments on commit c6ddd69

Please sign in to comment.