Skip to content

Commit cceb758

Browse files
[3.13] gh-118846: Fix free-threading test failures when run sequentially (GH-118864) (#118927)
The free-threaded build currently immortalizes some objects once the first thread is started. This can lead to test failures depending on the order in which tests are run. This PR addresses those failures by suppressing immortalization or skipping the affected tests. (cherry picked from commit b309c8e) Co-authored-by: Sam Gross <colesbury@gmail.com>
1 parent b3074f0 commit cceb758

File tree

8 files changed

+35
-6
lines changed

8 files changed

+35
-6
lines changed

Diff for: Lib/test/seq_tests.py

+1
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,7 @@ def test_pickle(self):
426426
self.assertEqual(lst2, lst)
427427
self.assertNotEqual(id(lst2), id(lst))
428428

429+
@support.suppress_immortalization()
429430
def test_free_after_iterating(self):
430431
support.check_free_after_iterating(self, iter, self.type2test)
431432
support.check_free_after_iterating(self, reversed, self.type2test)

Diff for: Lib/test/test_capi/test_misc.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
from test.support import threading_helper
2727
from test.support import warnings_helper
2828
from test.support import requires_limited_api
29-
from test.support import requires_gil_enabled, expected_failure_if_gil_disabled
29+
from test.support import suppress_immortalization
30+
from test.support import expected_failure_if_gil_disabled
3031
from test.support import Py_GIL_DISABLED
3132
from test.support.script_helper import assert_python_failure, assert_python_ok, run_python_until_end
3233
try:
@@ -481,6 +482,7 @@ def test_heap_ctype_doc_and_text_signature(self):
481482
def test_null_type_doc(self):
482483
self.assertEqual(_testcapi.NullTpDocType.__doc__, None)
483484

485+
@suppress_immortalization()
484486
def test_subclass_of_heap_gc_ctype_with_tpdealloc_decrefs_once(self):
485487
class HeapGcCTypeSubclass(_testcapi.HeapGcCType):
486488
def __init__(self):
@@ -498,6 +500,7 @@ def __init__(self):
498500
del subclass_instance
499501
self.assertEqual(type_refcnt - 1, sys.getrefcount(HeapGcCTypeSubclass))
500502

503+
@suppress_immortalization()
501504
def test_subclass_of_heap_gc_ctype_with_del_modifying_dunder_class_only_decrefs_once(self):
502505
class A(_testcapi.HeapGcCType):
503506
def __init__(self):

Diff for: Lib/test/test_descr.py

+1
Original file line numberDiff line numberDiff line change
@@ -5014,6 +5014,7 @@ def __new__(cls):
50145014
cls.lst = [2**i for i in range(10000)]
50155015
X.descr
50165016

5017+
@support.suppress_immortalization()
50175018
def test_remove_subclass(self):
50185019
# bpo-46417: when the last subclass of a type is deleted,
50195020
# remove_subclass() clears the internal dictionary of subclasses:

Diff for: Lib/test/test_gc.py

+18-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
from test import support
44
from test.support import (verbose, refcount_test,
55
cpython_only, requires_subprocess,
6-
requires_gil_enabled)
6+
requires_gil_enabled, suppress_immortalization,
7+
Py_GIL_DISABLED)
78
from test.support.import_helper import import_module
89
from test.support.os_helper import temp_dir, TESTFN, unlink
910
from test.support.script_helper import assert_python_ok, make_script
@@ -109,6 +110,7 @@ def test_tuple(self):
109110
del l
110111
self.assertEqual(gc.collect(), 2)
111112

113+
@suppress_immortalization()
112114
def test_class(self):
113115
class A:
114116
pass
@@ -117,6 +119,7 @@ class A:
117119
del A
118120
self.assertNotEqual(gc.collect(), 0)
119121

122+
@suppress_immortalization()
120123
def test_newstyleclass(self):
121124
class A(object):
122125
pass
@@ -133,6 +136,7 @@ class A:
133136
del a
134137
self.assertNotEqual(gc.collect(), 0)
135138

139+
@suppress_immortalization()
136140
def test_newinstance(self):
137141
class A(object):
138142
pass
@@ -219,6 +223,7 @@ class B(object):
219223
self.fail("didn't find obj in garbage (finalizer)")
220224
gc.garbage.remove(obj)
221225

226+
@suppress_immortalization()
222227
def test_function(self):
223228
# Tricky: f -> d -> f, code should call d.clear() after the exec to
224229
# break the cycle.
@@ -561,6 +566,7 @@ def test_get_referents(self):
561566

562567
self.assertEqual(gc.get_referents(1, 'a', 4j), [])
563568

569+
@suppress_immortalization()
564570
def test_is_tracked(self):
565571
# Atomic built-in types are not tracked, user-defined objects and
566572
# mutable containers are.
@@ -598,7 +604,9 @@ class UserFloatSlots(float):
598604
class UserIntSlots(int):
599605
__slots__ = ()
600606

601-
self.assertTrue(gc.is_tracked(gc))
607+
if not Py_GIL_DISABLED:
608+
# gh-117783: modules may be immortalized in free-threaded build
609+
self.assertTrue(gc.is_tracked(gc))
602610
self.assertTrue(gc.is_tracked(UserClass))
603611
self.assertTrue(gc.is_tracked(UserClass()))
604612
self.assertTrue(gc.is_tracked(UserInt()))
@@ -1347,6 +1355,10 @@ def callback(ignored):
13471355
junk = []
13481356
i = 0
13491357
detector = GC_Detector()
1358+
if Py_GIL_DISABLED:
1359+
# The free-threaded build doesn't have multiple generations, so
1360+
# just trigger a GC manually.
1361+
gc.collect()
13501362
while not detector.gc_happened:
13511363
i += 1
13521364
if i > 10000:
@@ -1415,6 +1427,10 @@ def __del__(self):
14151427
detector = GC_Detector()
14161428
junk = []
14171429
i = 0
1430+
if Py_GIL_DISABLED:
1431+
# The free-threaded build doesn't have multiple generations, so
1432+
# just trigger a GC manually.
1433+
gc.collect()
14181434
while not detector.gc_happened:
14191435
i += 1
14201436
if i > 10000:

Diff for: Lib/test/test_inspect/test_inspect.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
except ImportError:
3535
ThreadPoolExecutor = None
3636

37-
from test.support import cpython_only, import_helper
37+
from test.support import cpython_only, import_helper, suppress_immortalization
3838
from test.support import MISSING_C_DOCSTRINGS, ALWAYS_EQ
3939
from test.support.import_helper import DirsOnSysPath, ready_to_import
4040
from test.support.os_helper import TESTFN, temp_cwd
@@ -768,6 +768,7 @@ def test_getfile_builtin_function_or_method(self):
768768
inspect.getfile(list.append)
769769
self.assertIn('expected, got', str(e_append.exception))
770770

771+
@suppress_immortalization()
771772
def test_getfile_class_without_module(self):
772773
class CM(type):
773774
@property
@@ -2430,6 +2431,7 @@ def __getattribute__(self, attr):
24302431

24312432
self.assertFalse(test.called)
24322433

2434+
@suppress_immortalization()
24332435
def test_cache_does_not_cause_classes_to_persist(self):
24342436
# regression test for gh-118013:
24352437
# check that the internal _shadowed_dict cache does not cause

Diff for: Lib/test/test_module/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import weakref
55
from test.support import gc_collect
66
from test.support import import_helper
7+
from test.support import suppress_immortalization
78
from test.support.script_helper import assert_python_ok
89

910
import sys
@@ -103,6 +104,7 @@ def f():
103104
gc_collect()
104105
self.assertEqual(f().__dict__["bar"], 4)
105106

107+
@suppress_immortalization()
106108
def test_clear_dict_in_ref_cycle(self):
107109
destroyed = []
108110
m = ModuleType("foo")
@@ -118,6 +120,7 @@ def __del__(self):
118120
gc_collect()
119121
self.assertEqual(destroyed, [1])
120122

123+
@suppress_immortalization()
121124
def test_weakref(self):
122125
m = ModuleType("foo")
123126
wr = weakref.ref(m)

Diff for: Lib/test/test_trace.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import os
22
from pickle import dump
33
import sys
4-
from test.support import captured_stdout, requires_resource
4+
from test.support import captured_stdout, requires_resource, requires_gil_enabled
55
from test.support.os_helper import (TESTFN, rmtree, unlink)
66
from test.support.script_helper import assert_python_ok, assert_python_failure
77
import textwrap
@@ -301,6 +301,7 @@ def test_loop_caller_importing(self):
301301

302302
@unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
303303
'pre-existing trace function throws off measurements')
304+
@requires_gil_enabled("gh-117783: immortalization of types affects traced method names")
304305
def test_inst_method_calling(self):
305306
obj = TracedClass(20)
306307
self.tracer.runfunc(obj.inst_method_calling, 1)
@@ -334,6 +335,7 @@ def setUp(self):
334335

335336
@unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
336337
'pre-existing trace function throws off measurements')
338+
@requires_gil_enabled("gh-117783: immortalization of types affects traced method names")
337339
def test_loop_caller_importing(self):
338340
self.tracer.runfunc(traced_func_importing_caller, 1)
339341

Diff for: Lib/test/test_zoneinfo/test_zoneinfo.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from datetime import date, datetime, time, timedelta, timezone
1818
from functools import cached_property
1919

20-
from test.support import MISSING_C_DOCSTRINGS
20+
from test.support import MISSING_C_DOCSTRINGS, requires_gil_enabled
2121
from test.test_zoneinfo import _support as test_support
2222
from test.test_zoneinfo._support import OS_ENV_LOCK, TZPATH_TEST_LOCK, ZoneInfoTestBase
2323
from test.support.import_helper import import_module, CleanImport
@@ -1931,6 +1931,7 @@ def test_cache_location(self):
19311931
self.assertFalse(hasattr(c_zoneinfo.ZoneInfo, "_weak_cache"))
19321932
self.assertTrue(hasattr(py_zoneinfo.ZoneInfo, "_weak_cache"))
19331933

1934+
@requires_gil_enabled("gh-117783: types may be immortalized")
19341935
def test_gc_tracked(self):
19351936
import gc
19361937

0 commit comments

Comments
 (0)