Skip to content

[3.7] bpo-36560: regrtest: don't collect the GC twice (GH-12747) #12749

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 43 additions & 26 deletions Lib/test/libregrtest/refleak.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def _get_dump(cls):
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.

Returns:
Expand All @@ -32,6 +32,10 @@ def dash_R(the_module, test, indirect_test, huntrleaks):
raise Exception("Tracking reference leaks requires a debug build "
"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.
fs = warnings.filters[:]
ps = copyreg.dispatch_table.copy()
Expand All @@ -57,31 +61,52 @@ def dash_R(the_module, test, indirect_test, huntrleaks):
def get_pooled_int(value):
return int_pool.setdefault(value, value)

nwarmup, ntracked, fname = huntrleaks
nwarmup, ntracked, fname = ns.huntrleaks
fname = os.path.join(support.SAVEDCWD, fname)
repcount = nwarmup + ntracked

# Pre-allocate to ensure that the loop doesn't allocate anything new
rep_range = list(range(repcount))
rc_deltas = [0] * repcount
alloc_deltas = [0] * repcount
fd_deltas = [0] * repcount
getallocatedblocks = sys.getallocatedblocks
gettotalrefcount = sys.gettotalrefcount
fd_count = support.fd_count

print("beginning", repcount, "repetitions", file=sys.stderr)
print(("1234567890"*(repcount//10 + 1))[:repcount], file=sys.stderr,
flush=True)
# initialize variables to make pyflakes quiet
rc_before = alloc_before = fd_before = 0
for i in range(repcount):
indirect_test()
alloc_after, rc_after, fd_after = dash_R_cleanup(fs, ps, pic, zdc,
abcs)
print('.', end='', file=sys.stderr, flush=True)
if i >= nwarmup:
rc_deltas[i] = get_pooled_int(rc_after - rc_before)
alloc_deltas[i] = get_pooled_int(alloc_after - alloc_before)
fd_deltas[i] = get_pooled_int(fd_after - fd_before)

if not ns.quiet:
print("beginning", repcount, "repetitions", file=sys.stderr)
print(("1234567890"*(repcount//10 + 1))[:repcount], file=sys.stderr,
flush=True)

dash_R_cleanup(fs, ps, pic, zdc, abcs)

for i in rep_range:
test_func()
dash_R_cleanup(fs, ps, pic, zdc, abcs)

# dash_R_cleanup() ends with collecting cyclic trash:
# read memory statistics immediately after.
alloc_after = getallocatedblocks()
rc_after = gettotalrefcount()
fd_after = fd_count()

if not ns.quiet:
print('.', end='', file=sys.stderr, flush=True)

rc_deltas[i] = get_pooled_int(rc_after - rc_before)
alloc_deltas[i] = get_pooled_int(alloc_after - alloc_before)
fd_deltas[i] = get_pooled_int(fd_after - fd_before)

alloc_before = alloc_after
rc_before = rc_after
fd_before = fd_after
print(file=sys.stderr)

if not ns.quiet:
print(file=sys.stderr)

# These checkers return False on success, True on failure
def check_rc_deltas(deltas):
Expand Down Expand Up @@ -112,7 +137,7 @@ def check_fd_deltas(deltas):
deltas = deltas[nwarmup:]
if checker(deltas):
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)
with open(fname, "a") as refrep:
print(msg, file=refrep)
Expand All @@ -122,7 +147,7 @@ def check_fd_deltas(deltas):


def dash_R_cleanup(fs, ps, pic, zdc, abcs):
import gc, copyreg
import copyreg
import collections.abc

# Restore some original values.
Expand Down Expand Up @@ -154,16 +179,8 @@ def dash_R_cleanup(fs, ps, pic, zdc, abcs):

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():
import gc

# Clear the warnings registry, so they can be displayed again
for mod in sys.modules.values():
if hasattr(mod, '__warningregistry__'):
Expand Down Expand Up @@ -256,7 +273,7 @@ def clear_caches():
for f in typing._cleanups:
f()

gc.collect()
support.gc_collect()


def warm_caches():
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/libregrtest/runtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ def test_runner():
raise Exception("errors while loading tests")
support.run_unittest(tests)
if ns.huntrleaks:
refleak = dash_R(the_module, test, test_runner, ns.huntrleaks)
refleak = dash_R(ns, the_module, test, test_runner)
else:
test_runner()
test_time = time.perf_counter() - start_time
Expand Down
6 changes: 0 additions & 6 deletions Lib/test/libregrtest/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
except ImportError:
gc = None

from test.libregrtest.refleak import warm_caches


def setup_tests(ns):
try:
Expand Down Expand Up @@ -79,10 +77,6 @@ def setup_tests(ns):
if ns.huntrleaks:
unittest.BaseTestSuite._cleanup = False

# Avoid false positives due to various caches
# filling slowly with random data:
warm_caches()

if ns.memlimit is not None:
support.set_memlimit(ns.memlimit)

Expand Down
Original file line number Diff line number Diff line change
@@ -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.