From daff555f45c5c79e853adec93d9389db2d1632bf Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Mon, 17 Aug 2026 14:38:12 -0700 Subject: [PATCH 1/2] gh-155981: Store refleak deltas in arrays 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. --- Lib/test/libregrtest/refleak.py | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/Lib/test/libregrtest/refleak.py b/Lib/test/libregrtest/refleak.py index e7da17e500ead96..aa2bad748938420 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 (bpo-31217, 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 = '.' @@ -217,8 +213,8 @@ def check_fd_deltas(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(deltas[warmups:]) failing = checker(deltas) suspicious = any(deltas) if failing or suspicious: From 1be318837a7d4fb52c0ddca0a846937719b07feb Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Mon, 17 Aug 2026 16:39:53 -0700 Subject: [PATCH 2/2] Fix mypy error. --- Lib/test/libregrtest/refleak.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/libregrtest/refleak.py b/Lib/test/libregrtest/refleak.py index aa2bad748938420..d156acebcab41f9 100644 --- a/Lib/test/libregrtest/refleak.py +++ b/Lib/test/libregrtest/refleak.py @@ -208,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; convert to a list for reporting - deltas = list(deltas[warmups:]) + deltas = list(raw_deltas[warmups:]) failing = checker(deltas) suspicious = any(deltas) if failing or suspicious: