mirror of
https://github.com/python/cpython.git
synced 2025-08-02 16:13:13 +00:00
gh-117783: Immortalize objects that use deferred reference counting (#118112)
Deferred reference counting is not fully implemented yet. As a temporary measure, we immortalize objects that would use deferred reference counting to avoid multi-threaded scaling bottlenecks. This is only performed in the free-threaded build once the first non-main thread is started. Additionally, some tests, including refleak tests, suppress this behavior.
This commit is contained in:
parent
8d4b756fd3
commit
7ccacb220d
13 changed files with 134 additions and 8 deletions
|
@ -296,8 +296,9 @@ class _ExecutorManagerThread(threading.Thread):
|
|||
# if there is no pending work item.
|
||||
def weakref_cb(_,
|
||||
thread_wakeup=self.thread_wakeup,
|
||||
shutdown_lock=self.shutdown_lock):
|
||||
mp.util.debug('Executor collected: triggering callback for'
|
||||
shutdown_lock=self.shutdown_lock,
|
||||
mp_util_debug=mp.util.debug):
|
||||
mp_util_debug('Executor collected: triggering callback for'
|
||||
' QueueManager wakeup')
|
||||
with shutdown_lock:
|
||||
thread_wakeup.wakeup()
|
||||
|
|
|
@ -7,7 +7,8 @@ import sysconfig
|
|||
import time
|
||||
import trace
|
||||
|
||||
from test.support import os_helper, MS_WINDOWS, flush_std_streams
|
||||
from test.support import (os_helper, MS_WINDOWS, flush_std_streams,
|
||||
suppress_immortalization)
|
||||
|
||||
from .cmdline import _parse_args, Namespace
|
||||
from .findtests import findtests, split_test_packages, list_cases
|
||||
|
@ -526,7 +527,10 @@ class Regrtest:
|
|||
if self.num_workers:
|
||||
self._run_tests_mp(runtests, self.num_workers)
|
||||
else:
|
||||
self.run_tests_sequentially(runtests)
|
||||
# gh-117783: don't immortalize deferred objects when tracking
|
||||
# refleaks. Only releveant for the free-threaded build.
|
||||
with suppress_immortalization(runtests.hunt_refleak):
|
||||
self.run_tests_sequentially(runtests)
|
||||
|
||||
coverage = self.results.get_coverage_results()
|
||||
self.display_result(runtests)
|
||||
|
|
|
@ -303,7 +303,10 @@ def run_single_test(test_name: TestName, runtests: RunTests) -> TestResult:
|
|||
result = TestResult(test_name)
|
||||
pgo = runtests.pgo
|
||||
try:
|
||||
_runtest(result, runtests)
|
||||
# gh-117783: don't immortalize deferred objects when tracking
|
||||
# refleaks. Only releveant for the free-threaded build.
|
||||
with support.suppress_immortalization(runtests.hunt_refleak):
|
||||
_runtest(result, runtests)
|
||||
except:
|
||||
if not pgo:
|
||||
msg = traceback.format_exc()
|
||||
|
|
|
@ -516,6 +516,25 @@ def has_no_debug_ranges():
|
|||
def requires_debug_ranges(reason='requires co_positions / debug_ranges'):
|
||||
return unittest.skipIf(has_no_debug_ranges(), reason)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def suppress_immortalization(suppress=True):
|
||||
"""Suppress immortalization of deferred objects."""
|
||||
try:
|
||||
import _testinternalcapi
|
||||
except ImportError:
|
||||
yield
|
||||
return
|
||||
|
||||
if not suppress:
|
||||
yield
|
||||
return
|
||||
|
||||
old_values = _testinternalcapi.set_immortalize_deferred(False)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
_testinternalcapi.set_immortalize_deferred(*old_values)
|
||||
|
||||
MS_WINDOWS = (sys.platform == 'win32')
|
||||
|
||||
# Is not actually used in tests, but is kept for compatibility.
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import unittest
|
||||
|
||||
from contextlib import contextmanager, ExitStack
|
||||
from test.support import catch_unraisable_exception, import_helper, gc_collect
|
||||
from test.support import (
|
||||
catch_unraisable_exception, import_helper,
|
||||
gc_collect, suppress_immortalization)
|
||||
|
||||
|
||||
# Skip this test if the _testcapi module isn't available.
|
||||
|
@ -382,6 +384,7 @@ class TestCodeObjectWatchers(unittest.TestCase):
|
|||
self.assertEqual(
|
||||
exp_destroyed_1, _testcapi.get_code_watcher_num_destroyed_events(1))
|
||||
|
||||
@suppress_immortalization()
|
||||
def test_code_object_events_dispatched(self):
|
||||
# verify that all counts are zero before any watchers are registered
|
||||
self.assert_event_counts(0, 0, 0, 0)
|
||||
|
@ -428,6 +431,7 @@ class TestCodeObjectWatchers(unittest.TestCase):
|
|||
self.assertIsNone(cm.unraisable.object)
|
||||
self.assertEqual(str(cm.unraisable.exc_value), "boom!")
|
||||
|
||||
@suppress_immortalization()
|
||||
def test_dealloc_error(self):
|
||||
co = _testcapi.code_newempty("test_watchers", "dummy0", 0)
|
||||
with self.code_watcher(2):
|
||||
|
|
|
@ -141,7 +141,8 @@ except ImportError:
|
|||
ctypes = None
|
||||
from test.support import (cpython_only,
|
||||
check_impl_detail, requires_debug_ranges,
|
||||
gc_collect, Py_GIL_DISABLED)
|
||||
gc_collect, Py_GIL_DISABLED,
|
||||
suppress_immortalization)
|
||||
from test.support.script_helper import assert_python_ok
|
||||
from test.support import threading_helper, import_helper
|
||||
from test.support.bytecode_helper import instructions_with_positions
|
||||
|
@ -577,6 +578,7 @@ class CodeConstsTest(unittest.TestCase):
|
|||
|
||||
class CodeWeakRefTest(unittest.TestCase):
|
||||
|
||||
@suppress_immortalization()
|
||||
def test_basic(self):
|
||||
# Create a code object in a clean environment so that we know we have
|
||||
# the only reference to it left.
|
||||
|
@ -827,6 +829,7 @@ if check_impl_detail(cpython=True) and ctypes is not None:
|
|||
self.assertEqual(GetExtra(f.__code__, FREE_INDEX+100,
|
||||
ctypes.c_voidp(100)), 0)
|
||||
|
||||
@suppress_immortalization()
|
||||
def test_free_called(self):
|
||||
# Verify that the provided free function gets invoked
|
||||
# when the code object is cleaned up.
|
||||
|
@ -854,6 +857,7 @@ if check_impl_detail(cpython=True) and ctypes is not None:
|
|||
del f
|
||||
|
||||
@threading_helper.requires_working_threading()
|
||||
@suppress_immortalization()
|
||||
def test_free_different_thread(self):
|
||||
# Freeing a code object on a different thread then
|
||||
# where the co_extra was set should be safe.
|
||||
|
|
|
@ -1833,6 +1833,7 @@ class TestLRU:
|
|||
return 1
|
||||
self.assertEqual(f.cache_parameters(), {'maxsize': 1000, "typed": True})
|
||||
|
||||
@support.suppress_immortalization()
|
||||
def test_lru_cache_weakrefable(self):
|
||||
@self.module.lru_cache
|
||||
def test_function(x):
|
||||
|
|
|
@ -13,7 +13,7 @@ import random
|
|||
import textwrap
|
||||
|
||||
from test import support
|
||||
from test.support import script_helper, ALWAYS_EQ
|
||||
from test.support import script_helper, ALWAYS_EQ, suppress_immortalization
|
||||
from test.support import gc_collect
|
||||
from test.support import import_helper
|
||||
from test.support import threading_helper
|
||||
|
@ -651,6 +651,7 @@ class ReferencesTestCase(TestBase):
|
|||
# deallocation of c2.
|
||||
del c2
|
||||
|
||||
@suppress_immortalization()
|
||||
def test_callback_in_cycle(self):
|
||||
import gc
|
||||
|
||||
|
@ -743,6 +744,7 @@ class ReferencesTestCase(TestBase):
|
|||
del c1, c2, C, D
|
||||
gc.collect()
|
||||
|
||||
@suppress_immortalization()
|
||||
def test_callback_in_cycle_resurrection(self):
|
||||
import gc
|
||||
|
||||
|
@ -878,6 +880,7 @@ class ReferencesTestCase(TestBase):
|
|||
# No exception should be raised here
|
||||
gc.collect()
|
||||
|
||||
@suppress_immortalization()
|
||||
def test_classes(self):
|
||||
# Check that classes are weakrefable.
|
||||
class A(object):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue