diff --git a/Include/objimpl.h b/Include/objimpl.h --- a/Include/objimpl.h +++ b/Include/objimpl.h @@ -98,6 +98,9 @@ PyAPI_FUNC(void *) PyObject_Malloc(size_ PyAPI_FUNC(void *) PyObject_Realloc(void *, size_t); PyAPI_FUNC(void) PyObject_Free(void *); +/* This is the number of allocated memory blocks, regardless of size */ +PyAPI_DATA(Py_ssize_t) _Py_AllocedBlocks; + /* Macros */ #ifdef WITH_PYMALLOC diff --git a/Lib/test/regrtest.py b/Lib/test/regrtest.py --- a/Lib/test/regrtest.py +++ b/Lib/test/regrtest.py @@ -610,7 +610,7 @@ def main(tests=None, testdir=None, verbo sys.exit(2) from queue import Queue from subprocess import Popen, PIPE - debug_output_pat = re.compile(r"\[\d+ refs\]$") + debug_output_pat = re.compile(r"\[\d+ refs, \d+ blocks\]$") output = Queue() def tests_and_args(): for test in tests: @@ -1317,33 +1317,42 @@ def dash_R(the_module, test, indirect_te del sys.modules[the_module.__name__] exec('import ' + the_module.__name__) - deltas = [] nwarmup, ntracked, fname = huntrleaks fname = os.path.join(support.SAVEDCWD, fname) repcount = nwarmup + ntracked + rc_deltas = [0] * repcount + alloc_deltas = [0] * repcount + print("beginning", repcount, "repetitions", file=sys.stderr) print(("1234567890"*(repcount//10 + 1))[:repcount], file=sys.stderr) sys.stderr.flush() - dash_R_cleanup(fs, ps, pic, zdc, abcs) for i in range(repcount): + dash_R_cleanup(fs, ps, pic, zdc, abcs) rc_before = sys.gettotalrefcount() + alloc_before = sys.getallocedblocks() run_the_test() + dash_R_cleanup(fs, ps, pic, zdc, abcs) + rc_after = sys.gettotalrefcount() + alloc_after = sys.getallocedblocks() sys.stderr.write('.') sys.stderr.flush() - dash_R_cleanup(fs, ps, pic, zdc, abcs) - rc_after = sys.gettotalrefcount() if i >= nwarmup: - deltas.append(rc_after - rc_before) + rc_deltas[i] = rc_after - rc_before + alloc_deltas[i] = alloc_after - alloc_before print(file=sys.stderr) - if any(deltas): - msg = '%s leaked %s references, sum=%s' % (test, deltas, sum(deltas)) - print(msg, file=sys.stderr) - sys.stderr.flush() - with open(fname, "a") as refrep: - print(msg, file=refrep) - refrep.flush() - return True - return False + ret = False + for deltas, item_name in [ + (rc_deltas, 'references'), (alloc_deltas, 'memory blocks')]: + if any(deltas): + msg = '%s leaked %s %s, sum=%s' % ( + test, deltas[nwarmup:], item_name, sum(deltas)) + print(msg, file=sys.stderr) + sys.stderr.flush() + with open(fname, "a") as refrep: + print(msg, file=refrep) + refrep.flush() + ret = True + return ret def dash_R_cleanup(fs, ps, pic, zdc, abcs): import gc, copyreg diff --git a/Lib/test/support.py b/Lib/test/support.py --- a/Lib/test/support.py +++ b/Lib/test/support.py @@ -1578,7 +1578,7 @@ def strip_python_stderr(stderr): This will typically be run on the result of the communicate() method of a subprocess.Popen object. """ - stderr = re.sub(br"\[\d+ refs\]\r?\n?$", b"", stderr).strip() + stderr = re.sub(br"\[\d+ refs, \d+ blocks\]\r?\n?$", b"", stderr).strip() return stderr def args_from_interpreter_flags(): diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c --- a/Objects/obmalloc.c +++ b/Objects/obmalloc.c @@ -527,6 +527,8 @@ static size_t ntimes_arena_allocated = 0 static size_t narenas_highwater = 0; #endif +Py_ssize_t _Py_AllocedBlocks = 0; + /* Allocate a new arena. If we run out of memory, return NULL. Else * allocate a new arena, and return the address of an arena_object * describing the new arena. It's expected that the caller will set @@ -784,6 +786,8 @@ PyObject_Malloc(size_t nbytes) if (nbytes > PY_SSIZE_T_MAX) return NULL; + _Py_AllocedBlocks++; + /* * This implicitly redirects malloc(0). */ @@ -957,7 +961,12 @@ redirect: */ if (nbytes == 0) nbytes = 1; - return (void *)malloc(nbytes); + { + void *result = malloc(nbytes); + if (!result) + _Py_AllocedBlocks--; + return result; + } } /* free */ @@ -977,6 +986,8 @@ PyObject_Free(void *p) if (p == NULL) /* free(NULL) has no effect */ return; + _Py_AllocedBlocks--; + #ifdef WITH_VALGRIND if (UNLIKELY(running_on_valgrind > 0)) goto redirect; diff --git a/Python/pythonrun.c b/Python/pythonrun.c --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -38,9 +38,10 @@ #ifndef Py_REF_DEBUG #define PRINT_TOTAL_REFS() #else /* Py_REF_DEBUG */ -#define PRINT_TOTAL_REFS() fprintf(stderr, \ - "[%" PY_FORMAT_SIZE_T "d refs]\n", \ - _Py_GetRefTotal()) +#define PRINT_TOTAL_REFS() fprintf(stderr, \ + "[%" PY_FORMAT_SIZE_T "d refs, " \ + "%" PY_FORMAT_SIZE_T "d blocks]\n", \ + _Py_GetRefTotal(), _Py_AllocedBlocks) #endif #ifdef __cplusplus diff --git a/Python/sysmodule.c b/Python/sysmodule.c --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -894,6 +894,12 @@ one higher than you might expect, becaus reference as an argument to getrefcount()." ); +static PyObject * +sys_getallocedblocks(PyObject *self) +{ + return PyLong_FromSsize_t(_Py_AllocedBlocks); +} + #ifdef COUNT_ALLOCS static PyObject * sys_getcounts(PyObject *self) @@ -1055,6 +1061,7 @@ static PyMethodDef sys_methods[] = { #ifdef Py_REF_DEBUG {"gettotalrefcount", (PyCFunction)sys_gettotalrefcount, METH_NOARGS}, #endif + {"getallocedblocks", (PyCFunction)sys_getallocedblocks, METH_NOARGS}, {"getrefcount", (PyCFunction)sys_getrefcount, METH_O, getrefcount_doc}, {"getrecursionlimit", (PyCFunction)sys_getrecursionlimit, METH_NOARGS, getrecursionlimit_doc},