https://github.com/python/cpython/commit/86c17926b7c97024f162cde6a6e8f72424b3dd8a
commit: 86c17926b7c97024f162cde6a6e8f72424b3dd8a
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-09-17T16:32:27+02:00
summary:
gh-154137, regrtest: Check for Windows handle leaks (#154140)
files:
A Misc/NEWS.d/next/Tests/2026-07-19-15-59-41.gh-issue-154137.N689bE.rst
M Lib/test/libregrtest/refleak.py
M Lib/test/test_regrtest.py
diff --git a/Lib/test/libregrtest/refleak.py b/Lib/test/libregrtest/refleak.py
index 45064115463c2fe..26b52e02aa237b3 100644
--- a/Lib/test/libregrtest/refleak.py
+++ b/Lib/test/libregrtest/refleak.py
@@ -114,12 +114,15 @@ def runtest_refleak(test_name, test_func,
rc_deltas = array('q', [0]) * repcount
alloc_deltas = array('q', [0]) * repcount
fd_deltas = array('q', [0]) * repcount
+ handle_deltas = array('q', [0]) * repcount
getallocatedblocks = sys.getallocatedblocks
gettotalrefcount = sys.gettotalrefcount
getunicodeinternedsize = sys.getunicodeinternedsize
fd_count = os_helper.fd_count
+ handle_count = os_helper.handle_count
# initialize variables to make pyflakes quiet
rc_before = alloc_before = fd_before = interned_immortal_before = 0
+ handle_before = 0
if not quiet:
print("beginning", repcount, "repetitions. Showing number of leaks "
@@ -154,13 +157,17 @@ def runtest_refleak(test_name, test_func,
alloc_after = getallocatedblocks() - interned_immortal_after
rc_after = gettotalrefcount()
fd_after = fd_count()
+ handle_after = handle_count()
rc_deltas[i] = rc_after - rc_before
alloc_deltas[i] = alloc_after - alloc_before
fd_deltas[i] = fd_after - fd_before
+ handle_deltas[i] = handle_after - handle_before
if not quiet:
- total_leaks = max(rc_deltas[i], alloc_deltas[i], fd_deltas[i])
+ # 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], handle_deltas[i])
if total_leaks <= 0:
symbol = '.'
elif total_leaks < 10:
@@ -178,6 +185,7 @@ def runtest_refleak(test_name, test_func,
alloc_before = alloc_after
rc_before = rc_after
fd_before = fd_after
+ handle_before = handle_after
interned_immortal_before = interned_immortal_after
restore_support_xml(xml_filename)
@@ -185,11 +193,21 @@ def runtest_refleak(test_name, test_func,
if not quiet:
print(file=sys.stderr)
+ if ('multiprocessing' in test_name
+ or 'concurrent_futures' in test_name):
+ # gh-154208: Disable check for Windows handle leaks when
+ # multiprocessing is used. There is a known race condition in
+ # multiprocessing causing handle leak. Disable the multiprocessing
+ # tests to be able to check for leaks for all other tests.
+ for i in range(len(handle_deltas)):
+ handle_deltas[i] = 0
+
failed = False
for raw_deltas, item_name in [
(rc_deltas, 'references'),
(alloc_deltas, 'memory blocks'),
- (fd_deltas, 'file descriptors')
+ (fd_deltas, 'file descriptors'),
+ (handle_deltas, 'handles'),
]:
# Ignore warmup runs; convert to a list for reporting
deltas = list(raw_deltas[warmups:])
diff --git a/Lib/test/test_regrtest.py b/Lib/test/test_regrtest.py
index 71edec40ade8079..53f0a2863dc4e45 100644
--- a/Lib/test/test_regrtest.py
+++ b/Lib/test/test_regrtest.py
@@ -1457,6 +1457,35 @@ def test_leak(self):
self.check_leak(code, 'file descriptors',
name='no_fd_leak', deltas=(1, -1, 0))
+ @unittest.skipUnless(support.Py_DEBUG, 'need a debug build')
+ @unittest.skipUnless(support.MS_WINDOWS, 'test specific to Windows')
+ def test_huntrleaks_handle_leak(self):
+ # test --huntrleaks for Windows handle leak
+ code = textwrap.dedent("""
+ import unittest
+ import _winapi
+
+ handle = None
+
+ class HandleLeakTest(unittest.TestCase):
+ def test_leak(self):
+ global handle
+ if handle is None:
+ handle = _winapi.CreateFile(
+ __file__, _winapi.GENERIC_READ,
+ 0, _winapi.NULL,
+ _winapi.OPEN_EXISTING,
+ 0, _winapi.NULL)
+ else:
+ hproc = _winapi.GetCurrentProcess()
+ copy = _winapi.DuplicateHandle(
+ hproc, handle,
+ hproc, 0, False,
+ _winapi.DUPLICATE_SAME_ACCESS)
+ # bug! the new handle is never closed
+ """)
+ self.check_leak(code, 'handles')
+
def test_list_tests(self):
# test --list-tests
tests = [self.create_test() for i in range(5)]
diff --git
a/Misc/NEWS.d/next/Tests/2026-07-19-15-59-41.gh-issue-154137.N689bE.rst
b/Misc/NEWS.d/next/Tests/2026-07-19-15-59-41.gh-issue-154137.N689bE.rst
new file mode 100644
index 000000000000000..39809e44f721856
--- /dev/null
+++ b/Misc/NEWS.d/next/Tests/2026-07-19-15-59-41.gh-issue-154137.N689bE.rst
@@ -0,0 +1 @@
+Check for Windows handle leaks in regrtest. Patch by Victor Stinner.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]