Skip to content

bpo-18174: regrtest -R 3:3 checks for handle leak #7827

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

Closed
wants to merge 2 commits into from
Closed
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
20 changes: 15 additions & 5 deletions Lib/test/libregrtest/refleak.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ def _get_dump(cls):
return (cls._abc_registry, cls._abc_cache,
cls._abc_negative_cache, cls._abc_negative_cache_version)

if sys.platform == 'win32':
from _winapi import GetProcessHandleCount as handle_count
else:
def handle_count():
return 0


def dash_R(the_module, test, indirect_test, huntrleaks):
"""Run a test multiple times, looking for reference leaks.
Expand Down Expand Up @@ -59,24 +65,27 @@ def get_pooled_int(value):
rc_deltas = [0] * repcount
alloc_deltas = [0] * repcount
fd_deltas = [0] * repcount
handle_deltas = [0] * repcount

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
rc_before = alloc_before = fd_before = handle_before = 0
for i in range(repcount):
indirect_test()
alloc_after, rc_after, fd_after = dash_R_cleanup(fs, ps, pic, zdc,
abcs)
result = dash_R_cleanup(fs, ps, pic, zdc, abcs)
alloc_after, rc_after, fd_after, handle_after = result
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)
handle_deltas[i] = get_pooled_int(handle_after - handle_before)
alloc_before = alloc_after
rc_before = rc_after
fd_before = fd_after
handle_before = handle_after
print(file=sys.stderr)

# These checkers return False on success, True on failure
Expand All @@ -102,7 +111,8 @@ def check_fd_deltas(deltas):
for 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)
(fd_deltas, 'file descriptors', check_fd_deltas),
(handle_deltas, 'handles', check_rc_deltas),
]:
# ignore warmup runs
deltas = deltas[nwarmup:]
Expand Down Expand Up @@ -154,7 +164,7 @@ def dash_R_cleanup(fs, ps, pic, zdc, abcs):
func1 = sys.getallocatedblocks
func2 = sys.gettotalrefcount
gc.collect()
return func1(), func2(), support.fd_count()
return func1(), func2(), support.fd_count(), handle_count()


def clear_caches():
Expand Down
22 changes: 22 additions & 0 deletions Lib/test/test_regrtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -857,6 +857,28 @@ def test_leak(self):
""")
self.check_leak(code, 'file descriptors')

@unittest.skipUnless(sys.platform == 'win32', 'specific to Windows')
@unittest.skipUnless(Py_DEBUG, 'need a debug build')
def test_huntrleaks_handle_leak(self):
# test --huntrleaks for file descriptor leak
code = textwrap.dedent("""
import os
import unittest
import _winapi

process = _winapi.GetCurrentProcess()
handle = process

class FDLeakTest(unittest.TestCase):
def test_leak(self):
handle2 = _winapi.DuplicateHandle(
process, handle, process,
0, 1,
_winapi.DUPLICATE_SAME_ACCESS)
# bug: never close handle2
""")
self.check_leak(code, 'handles')

def test_list_tests(self):
# test --list-tests
tests = [self.create_test() for i in range(5)]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
regrtest now also checks for leak of Windows handles. Initial patch written
by Richard Oudkerk.
28 changes: 28 additions & 0 deletions Modules/_winapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -1229,6 +1229,33 @@ _winapi_GetModuleFileName_impl(PyObject *module, HMODULE module_handle)
return PyUnicode_FromWideChar(filename, wcslen(filename));
}

/*[clinic input]
_winapi.GetProcessHandleCount

ProcessHandle: HANDLE(c_default="GetCurrentProcess()") = NULL
/

Return the number of open handles for the specified process.

Return the number of open handles for the process specified
by ProcessHandle. If ProcessHandle is not given then the
handle count for the current process is given.

[clinic start generated code]*/

static PyObject *
_winapi_GetProcessHandleCount_impl(PyObject *module, HANDLE ProcessHandle)
/*[clinic end generated code: output=af58910c23922016 input=089cb7546e598a2d]*/
{
DWORD HandleCount;

if (!GetProcessHandleCount(ProcessHandle, &HandleCount))
return PyErr_SetFromWindowsErr(0);

return PyLong_FromUnsignedLong(HandleCount);
}


/*[clinic input]
_winapi.GetStdHandle -> HANDLE

Expand Down Expand Up @@ -1714,6 +1741,7 @@ static PyMethodDef winapi_functions[] = {
_WINAPI_GETEXITCODEPROCESS_METHODDEF
_WINAPI_GETLASTERROR_METHODDEF
_WINAPI_GETMODULEFILENAME_METHODDEF
_WINAPI_GETPROCESSHANDLECOUNT_METHODDEF
_WINAPI_GETSTDHANDLE_METHODDEF
_WINAPI_GETVERSION_METHODDEF
_WINAPI_OPENPROCESS_METHODDEF
Expand Down
34 changes: 33 additions & 1 deletion Modules/clinic/_winapi.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.