mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
bpo-36560: Fix reference leak hunting in regrtest (GH-12744)
Fix reference leak hunting in regrtest: compute also deltas (of reference count, allocated memory blocks, file descriptor count) during warmup, to ensure that everything is initialized before starting to hunt reference leaks. Other changes: * Replace gc.collect() with support.gc_collect() * Move calls to read memory statistics from dash_R_cleanup() to dash_R() * Pass regrtest 'ns' to dash_R() * dash_R() is now more quiet with --quiet option (don't display progress). * Precompute the full range for "for it in range(repcount):" to ensure that the iteration doesn't allocate anything new. * dash_R() now is responsible to call warm_caches().
This commit is contained in:
parent
e16467af0b
commit
5aaac94eeb
4 changed files with 46 additions and 33 deletions
|
@ -18,7 +18,7 @@ except ImportError:
|
||||||
cls._abc_negative_cache, cls._abc_negative_cache_version)
|
cls._abc_negative_cache, cls._abc_negative_cache_version)
|
||||||
|
|
||||||
|
|
||||||
def dash_R(the_module, test, indirect_test, huntrleaks):
|
def dash_R(ns, the_module, test_name, test_func):
|
||||||
"""Run a test multiple times, looking for reference leaks.
|
"""Run a test multiple times, looking for reference leaks.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
@ -32,6 +32,10 @@ def dash_R(the_module, test, indirect_test, huntrleaks):
|
||||||
raise Exception("Tracking reference leaks requires a debug build "
|
raise Exception("Tracking reference leaks requires a debug build "
|
||||||
"of Python")
|
"of Python")
|
||||||
|
|
||||||
|
# Avoid false positives due to various caches
|
||||||
|
# filling slowly with random data:
|
||||||
|
warm_caches()
|
||||||
|
|
||||||
# Save current values for dash_R_cleanup() to restore.
|
# Save current values for dash_R_cleanup() to restore.
|
||||||
fs = warnings.filters[:]
|
fs = warnings.filters[:]
|
||||||
ps = copyreg.dispatch_table.copy()
|
ps = copyreg.dispatch_table.copy()
|
||||||
|
@ -57,30 +61,49 @@ def dash_R(the_module, test, indirect_test, huntrleaks):
|
||||||
def get_pooled_int(value):
|
def get_pooled_int(value):
|
||||||
return int_pool.setdefault(value, value)
|
return int_pool.setdefault(value, value)
|
||||||
|
|
||||||
nwarmup, ntracked, fname = huntrleaks
|
nwarmup, ntracked, fname = ns.huntrleaks
|
||||||
fname = os.path.join(support.SAVEDCWD, fname)
|
fname = os.path.join(support.SAVEDCWD, fname)
|
||||||
repcount = nwarmup + ntracked
|
repcount = nwarmup + ntracked
|
||||||
|
|
||||||
|
# Pre-allocate to ensure that the loop doesn't allocate anything new
|
||||||
|
rep_range = list(range(repcount))
|
||||||
rc_deltas = [0] * repcount
|
rc_deltas = [0] * repcount
|
||||||
alloc_deltas = [0] * repcount
|
alloc_deltas = [0] * repcount
|
||||||
fd_deltas = [0] * repcount
|
fd_deltas = [0] * repcount
|
||||||
|
getallocatedblocks = sys.getallocatedblocks
|
||||||
|
gettotalrefcount = sys.gettotalrefcount
|
||||||
|
fd_count = support.fd_count
|
||||||
|
|
||||||
|
# initialize variables to make pyflakes quiet
|
||||||
|
rc_before = alloc_before = fd_before = 0
|
||||||
|
|
||||||
|
if not ns.quiet:
|
||||||
print("beginning", repcount, "repetitions", file=sys.stderr)
|
print("beginning", repcount, "repetitions", file=sys.stderr)
|
||||||
print(("1234567890"*(repcount//10 + 1))[:repcount], file=sys.stderr,
|
print(("1234567890"*(repcount//10 + 1))[:repcount], file=sys.stderr,
|
||||||
flush=True)
|
flush=True)
|
||||||
# initialize variables to make pyflakes quiet
|
|
||||||
rc_before = alloc_before = fd_before = 0
|
for i in rep_range:
|
||||||
for i in range(repcount):
|
test_func()
|
||||||
indirect_test()
|
dash_R_cleanup(fs, ps, pic, zdc, abcs)
|
||||||
alloc_after, rc_after, fd_after = dash_R_cleanup(fs, ps, pic, zdc,
|
|
||||||
abcs)
|
# Collect cyclic trash and read memory statistics immediately after.
|
||||||
|
support.gc_collect()
|
||||||
|
alloc_after = getallocatedblocks()
|
||||||
|
rc_after = gettotalrefcount()
|
||||||
|
fd_after = fd_count()
|
||||||
|
|
||||||
|
if not ns.quiet:
|
||||||
print('.', end='', file=sys.stderr, flush=True)
|
print('.', end='', file=sys.stderr, flush=True)
|
||||||
if i >= nwarmup:
|
|
||||||
rc_deltas[i] = get_pooled_int(rc_after - rc_before)
|
rc_deltas[i] = get_pooled_int(rc_after - rc_before)
|
||||||
alloc_deltas[i] = get_pooled_int(alloc_after - alloc_before)
|
alloc_deltas[i] = get_pooled_int(alloc_after - alloc_before)
|
||||||
fd_deltas[i] = get_pooled_int(fd_after - fd_before)
|
fd_deltas[i] = get_pooled_int(fd_after - fd_before)
|
||||||
|
|
||||||
alloc_before = alloc_after
|
alloc_before = alloc_after
|
||||||
rc_before = rc_after
|
rc_before = rc_after
|
||||||
fd_before = fd_after
|
fd_before = fd_after
|
||||||
|
|
||||||
|
if not ns.quiet:
|
||||||
print(file=sys.stderr)
|
print(file=sys.stderr)
|
||||||
|
|
||||||
# These checkers return False on success, True on failure
|
# These checkers return False on success, True on failure
|
||||||
|
@ -112,7 +135,7 @@ def dash_R(the_module, test, indirect_test, huntrleaks):
|
||||||
deltas = deltas[nwarmup:]
|
deltas = deltas[nwarmup:]
|
||||||
if checker(deltas):
|
if checker(deltas):
|
||||||
msg = '%s leaked %s %s, sum=%s' % (
|
msg = '%s leaked %s %s, sum=%s' % (
|
||||||
test, deltas, item_name, sum(deltas))
|
test_name, deltas, item_name, sum(deltas))
|
||||||
print(msg, file=sys.stderr, flush=True)
|
print(msg, file=sys.stderr, flush=True)
|
||||||
with open(fname, "a") as refrep:
|
with open(fname, "a") as refrep:
|
||||||
print(msg, file=refrep)
|
print(msg, file=refrep)
|
||||||
|
@ -122,7 +145,7 @@ def dash_R(the_module, test, indirect_test, huntrleaks):
|
||||||
|
|
||||||
|
|
||||||
def dash_R_cleanup(fs, ps, pic, zdc, abcs):
|
def dash_R_cleanup(fs, ps, pic, zdc, abcs):
|
||||||
import gc, copyreg
|
import copyreg
|
||||||
import collections.abc
|
import collections.abc
|
||||||
|
|
||||||
# Restore some original values.
|
# Restore some original values.
|
||||||
|
@ -154,16 +177,8 @@ def dash_R_cleanup(fs, ps, pic, zdc, abcs):
|
||||||
|
|
||||||
clear_caches()
|
clear_caches()
|
||||||
|
|
||||||
# Collect cyclic trash and read memory statistics immediately after.
|
|
||||||
func1 = sys.getallocatedblocks
|
|
||||||
func2 = sys.gettotalrefcount
|
|
||||||
gc.collect()
|
|
||||||
return func1(), func2(), support.fd_count()
|
|
||||||
|
|
||||||
|
|
||||||
def clear_caches():
|
def clear_caches():
|
||||||
import gc
|
|
||||||
|
|
||||||
# Clear the warnings registry, so they can be displayed again
|
# Clear the warnings registry, so they can be displayed again
|
||||||
for mod in sys.modules.values():
|
for mod in sys.modules.values():
|
||||||
if hasattr(mod, '__warningregistry__'):
|
if hasattr(mod, '__warningregistry__'):
|
||||||
|
@ -256,7 +271,7 @@ def clear_caches():
|
||||||
for f in typing._cleanups:
|
for f in typing._cleanups:
|
||||||
f()
|
f()
|
||||||
|
|
||||||
gc.collect()
|
support.gc_collect()
|
||||||
|
|
||||||
|
|
||||||
def warm_caches():
|
def warm_caches():
|
||||||
|
|
|
@ -177,7 +177,7 @@ def runtest_inner(ns, test, display_failure=True):
|
||||||
raise Exception("errors while loading tests")
|
raise Exception("errors while loading tests")
|
||||||
support.run_unittest(tests)
|
support.run_unittest(tests)
|
||||||
if ns.huntrleaks:
|
if ns.huntrleaks:
|
||||||
refleak = dash_R(the_module, test, test_runner, ns.huntrleaks)
|
refleak = dash_R(ns, the_module, test, test_runner)
|
||||||
else:
|
else:
|
||||||
test_runner()
|
test_runner()
|
||||||
test_time = time.perf_counter() - start_time
|
test_time = time.perf_counter() - start_time
|
||||||
|
|
|
@ -10,8 +10,6 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
gc = None
|
gc = None
|
||||||
|
|
||||||
from test.libregrtest.refleak import warm_caches
|
|
||||||
|
|
||||||
|
|
||||||
def setup_tests(ns):
|
def setup_tests(ns):
|
||||||
try:
|
try:
|
||||||
|
@ -79,10 +77,6 @@ def setup_tests(ns):
|
||||||
if ns.huntrleaks:
|
if ns.huntrleaks:
|
||||||
unittest.BaseTestSuite._cleanup = False
|
unittest.BaseTestSuite._cleanup = False
|
||||||
|
|
||||||
# Avoid false positives due to various caches
|
|
||||||
# filling slowly with random data:
|
|
||||||
warm_caches()
|
|
||||||
|
|
||||||
if ns.memlimit is not None:
|
if ns.memlimit is not None:
|
||||||
support.set_memlimit(ns.memlimit)
|
support.set_memlimit(ns.memlimit)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Fix reference leak hunting in regrtest: compute also deltas (of reference
|
||||||
|
count, allocated memory blocks, file descriptor count) during warmup, to
|
||||||
|
ensure that everything is initialized before starting to hunt reference
|
||||||
|
leaks.
|
Loading…
Add table
Add a link
Reference in a new issue