import contextlib
import os
import sys
+import textwrap
import tracemalloc
import unittest
from unittest.mock import patch
_testinternalcapi = None
+DEFAULT_DOMAIN = 0
EMPTY_STRING_SIZE = sys.getsizeof(b'')
INVALID_NFRAME = (-1, 2**30)
release_gil)
return frames
- def untrack(self):
- _testcapi.tracemalloc_untrack(self.domain, self.ptr)
+ def untrack(self, release_gil=False):
+ _testcapi.tracemalloc_untrack(self.domain, self.ptr, release_gil)
def get_traced_memory(self):
# Get the traced size in the domain
self.assertEqual(self.get_traceback(),
tracemalloc.Traceback(frames))
- def test_untrack(self):
+ def check_untrack(self, release_gil):
tracemalloc.start()
self.track()
self.assertEqual(self.get_traced_memory(), self.size)
# untrack must remove the trace
- self.untrack()
+ self.untrack(release_gil)
self.assertIsNone(self.get_traceback())
self.assertEqual(self.get_traced_memory(), 0)
# calling _PyTraceMalloc_Untrack() multiple times must not crash
- self.untrack()
- self.untrack()
+ self.untrack(release_gil)
+ self.untrack(release_gil)
+
+ def test_untrack(self):
+ self.check_untrack(False)
+
+ def test_untrack_without_gil(self):
+ self.check_untrack(True)
def test_stop_track(self):
tracemalloc.start()
# gh-128679: Test fix for tracemalloc.stop() race condition
_testcapi.tracemalloc_track_race()
+ def test_late_untrack(self):
+ code = textwrap.dedent(f"""
+ from test import support
+ import tracemalloc
+ import _testcapi
+
+ class Tracked:
+ def __init__(self, domain, size):
+ self.domain = domain
+ self.ptr = id(self)
+ self.size = size
+ _testcapi.tracemalloc_track(self.domain, self.ptr, self.size)
+
+ def __del__(self, untrack=_testcapi.tracemalloc_untrack):
+ untrack(self.domain, self.ptr, 1)
+
+ domain = {DEFAULT_DOMAIN}
+ tracemalloc.start()
+ obj = Tracked(domain, 1024 * 1024)
+ support.late_deletion(obj)
+ """)
+ assert_python_ok("-c", code)
+
if __name__ == "__main__":
unittest.main()
{
unsigned int domain;
PyObject *ptr_obj;
+ int release_gil = 0;
- if (!PyArg_ParseTuple(args, "IO", &domain, &ptr_obj)) {
+ if (!PyArg_ParseTuple(args, "IO|i", &domain, &ptr_obj, &release_gil)) {
return NULL;
}
void *ptr = PyLong_AsVoidPtr(ptr_obj);
return NULL;
}
- int res = PyTraceMalloc_Untrack(domain, (uintptr_t)ptr);
+ int res;
+ if (release_gil) {
+ Py_BEGIN_ALLOW_THREADS
+ res = PyTraceMalloc_Untrack(domain, (uintptr_t)ptr);
+ Py_END_ALLOW_THREADS
+ }
+ else {
+ res = PyTraceMalloc_Untrack(domain, (uintptr_t)ptr);
+ }
if (res < 0) {
PyErr_SetString(PyExc_RuntimeError, "PyTraceMalloc_Untrack error");
return NULL;
/* Disable tracemalloc after all Python objects have been destroyed,
so it is possible to use tracemalloc in objects destructor. */
- _PyTraceMalloc_Fini();
+ _PyTraceMalloc_Stop();
/* Finalize any remaining import state */
// XXX Move these up to where finalize_modules() is currently.
finalize_interp_clear(tstate);
+ _PyTraceMalloc_Fini();
+
#ifdef Py_TRACE_REFS
/* Display addresses (& refcnts) of all objects still alive.
* An address can be used to find the repr of the object, printed
size_t size)
{
PyGILState_STATE gil_state = PyGILState_Ensure();
+ int result;
+
+ // gh-129185: Check before TABLES_LOCK() to support calls after
+ // _PyTraceMalloc_Fini().
+ if (!tracemalloc_config.tracing) {
+ result = -2;
+ goto done;
+ }
+
TABLES_LOCK();
- int result;
if (tracemalloc_config.tracing) {
result = tracemalloc_add_trace_unlocked(domain, ptr, size);
}
}
TABLES_UNLOCK();
+done:
PyGILState_Release(gil_state);
return result;
int
PyTraceMalloc_Untrack(unsigned int domain, uintptr_t ptr)
{
+ // Need the GIL to prevent races on the first 'tracing' test
+ PyGILState_STATE gil_state = PyGILState_Ensure();
+ int result;
+
+ // gh-129185: Check before TABLES_LOCK() to support calls after
+ // _PyTraceMalloc_Fini()
+ if (!tracemalloc_config.tracing) {
+ result = -2;
+ goto done;
+ }
+
TABLES_LOCK();
- int result;
if (tracemalloc_config.tracing) {
tracemalloc_remove_trace_unlocked(domain, ptr);
result = 0;
}
TABLES_UNLOCK();
+done:
+ PyGILState_Release(gil_state);
return result;
}