gh-155981: Store refleak deltas in arrays (gh-55982)
Store per-run deltas in array objects rather than lists of pooled
integers. Large, unique deltas could otherwise grow int_pool and make
the refleak checker report its own retained integers as reference leaks.
diff --git a/Lib/test/libregrtest/refleak.py b/Lib/test/libregrtest/refleak.py
index e7da17e..ffb8438 100644
--- a/Lib/test/libregrtest/refleak.py
+++ b/Lib/test/libregrtest/refleak.py
@@ -1,6 +1,7 @@
import os
import sys
import warnings
+from array import array
from inspect import isabstract
from typing import Any
import linecache
@@ -100,24 +101,20 @@ def runtest_refleak(test_name, test_func,
for obj in ByteString.__subclasses__() + [ByteString]: # type: ignore[attr-defined]
abcs[obj] = _get_dump(obj)[0]
- # bpo-31217: Integer pool to get a single integer object for the same
- # value. The pool is used to prevent false alarm when checking for memory
- # block leaks. Fill the pool with values in -1000..1000 which are the most
- # common (reference, memory block, file descriptor) differences.
- int_pool = {value: value for value in range(-1000, 1000)}
- def get_pooled_int(value):
- return int_pool.setdefault(value, value)
-
warmups = hunt_refleak.warmups
runs = hunt_refleak.runs
filename = hunt_refleak.filename
repcount = warmups + runs
- # Pre-allocate to ensure that the loop doesn't allocate anything new
+ # Pre-allocate to ensure that the loop doesn't allocate anything new.
+ # Store the deltas as raw values in arrays rather than as int objects in
+ # lists: each unique delta stored as an object would live until the end of
+ # the loop and show up in the following repetition's reference and memory
+ # block deltas (gh-75400, gh-155981).
rep_range = list(range(repcount))
- rc_deltas = [0] * repcount
- alloc_deltas = [0] * repcount
- fd_deltas = [0] * repcount
+ rc_deltas = array('q', [0]) * repcount
+ alloc_deltas = array('q', [0]) * repcount
+ fd_deltas = array('q', [0]) * repcount
getallocatedblocks = sys.getallocatedblocks
gettotalrefcount = sys.gettotalrefcount
getunicodeinternedsize = sys.getunicodeinternedsize
@@ -161,12 +158,11 @@ def get_pooled_int(value):
rc_after = gettotalrefcount()
fd_after = fd_count()
- 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)
+ rc_deltas[i] = rc_after - rc_before
+ alloc_deltas[i] = alloc_after - alloc_before
+ fd_deltas[i] = fd_after - fd_before
if not quiet:
- # use max, not sum, so total_leaks is one of the pooled ints
total_leaks = max(rc_deltas[i], alloc_deltas[i], fd_deltas[i])
if total_leaks <= 0:
symbol = '.'
@@ -212,13 +208,13 @@ def check_fd_deltas(deltas):
return any(deltas)
failed = False
- for deltas, item_name, checker in [
+ for raw_deltas, item_name, checker in [
(rc_deltas, 'references', check_rc_deltas),
(alloc_deltas, 'memory blocks', check_rc_deltas),
(fd_deltas, 'file descriptors', check_fd_deltas)
]:
- # ignore warmup runs
- deltas = deltas[warmups:]
+ # ignore warmup runs; convert to a list for reporting
+ deltas = list(raw_deltas[warmups:])
failing = checker(deltas)
suspicious = any(deltas)
if failing or suspicious: