8000 [3.12] gh-129185: Fix PyTraceMalloc_Untrack() at Python exit (#129191… · python/cpython@bb7c54d · GitHub
[go: up one dir, main page]

Skip to content

Commit bb7c54d

Browse files
authored
[3.12] gh-129185: Fix PyTraceMalloc_Untrack() at Python exit (#129191) (#129217) (#129221)
[3.13] gh-129185: Fix PyTraceMalloc_Untrack() at Python exit (#129191) (#129217) gh-129185: Fix PyTraceMalloc_Untrack() at Python exit (#129191) Support calling PyTraceMalloc_Track() and PyTraceMalloc_Untrack() during late Python finalization. * Call _PyTraceMalloc_Fini() later in Python finalization. * Test also PyTraceMalloc_Untrack() without the GIL * PyTraceMalloc_Untrack() now gets the GIL. * Test also PyTraceMalloc_Untrack() in test_tracemalloc_track_race(). (cherry picked from commit 46c7e13) (cherry picked from commit e3b3e01)
1 parent 880ad18 commit bb7c54d

File tree

5 files changed

+78
-14
lines changed

5 files changed

+78
-14
lines changed

Lib/test/test_tracemalloc.py

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import contextlib
22
import os
33
import sys
4+
import textwrap
45
import tracemalloc
56
import unittest
67
from unittest.mock import patch
@@ -16,6 +17,7 @@
1617
_testcapi = None
1718

1819

20+
DEFAULT_DOMAIN = 0
1921
EMPTY_STRING_SIZE = sys.getsizeof(b'')
2022
INVALID_NFRAME = (-1, 2**30)
2123

@@ -1020,8 +1022,8 @@ def track(self, release_gil=False, nframe=1):
10201022
release_gil)
10211023
return frames
10221024

1023-
def untrack(self):
1024-
_testcapi.tracemalloc_untrack(self.domain, self.ptr)
1025+
def untrack(self, release_gil=False):
1026+
_testcapi.tracemalloc_untrack(self.domain, self.ptr, release_gil)
10251027

10261028
def get_traced_memory(self):
10271029
# Get the traced size in the domain
@@ -1063,21 +1065,27 @@ def test_track_already_tracked(self):
10631065
self.assertEqual(self.get_traceback(),
10641066
tracemalloc.Traceback(frames))
10651067

1066-
def test_untrack(self):
1068+
def check_untrack(self, release_gil):
10671069
tracemalloc.start()
10681070

10691071
self.track()
10701072
self.assertIsNotNone(self.get_traceback())
10711073
self.assertEqual(self.get_traced_memory(), self.size)
10721074

10731075
# untrack must remove the trace
1074-
self.untrack()
1076+
self.untrack(release_gil)
10751077
self.assertIsNone(self.get_traceback())
10761078
self.assertEqual(self.get_traced_memory(), 0)
10771079

10781080
# calling _PyTraceMalloc_Untrack() multiple times must not crash
1079-
self.untrack()
1080-
self.untrack()
1081+
self.untrack(release_gil)
1082+
self.untrack(release_gil)
1083+
1084+
def test_untrack(self):
1085+
self.check_untrack(False)
1086+
1087+
def test_untrack_without_gil(self):
1088+
self.check_untrack(True)
10811089

10821090
def test_stop_track(self):
10831091
tracemalloc.start()
@@ -1103,6 +1111,29 @@ def test_tracemalloc_track_race(self):
11031111
# gh-128679: Test fix for tracemalloc.stop() race condition
11041112
_testcapi.tracemalloc_track_race()
11051113

1114+
def test_late_untrack(self):
1115+
code = textwrap.dedent(f"""
1116+
from test import support
1117+
import tracemalloc
1118+
import _testcapi
1119+
1120+
class Tracked:
1121+
def __init__(self, domain, size):
1122+
self.domain = domain
1123+
self.ptr = id(self)
1124+
self.size = size
1125+
_testcapi.tracemalloc_track(self.domain, self.ptr, self.size)
1126+
1127+
def __del__(self, untrack=_testcapi.tracemalloc_untrack):
1128+
untrack(self.domain, self.ptr, 1)
1129+
1130+
domain = {DEFAULT_DOMAIN}
1131+
tracemalloc.start()
1132+
obj = Tracked(domain, 1024 * 1024)
1133+
support.late_deletion(obj) B41A
1134+
""")
1135+
assert_python_ok("-c", code)
1136+
11061137

11071138
if __name__ == "__main__":
11081139
unittest.main()

Modules/_testcapi/mem.c

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -637,16 +637,25 @@ tracemalloc_untrack(PyObject *self, PyObject *args)
637637
{
638638
unsigned int domain;
639639
PyObject *ptr_obj;
640+
int release_gil = 0;
640641

641-
if (!PyArg_ParseTuple(args, "IO", &domain, &ptr_obj)) {
642+
if (!PyArg_ParseTuple(args, "IO|i", &domain, &ptr_obj, &release_gil)) {
642643
return NULL;
643644
}
644645
void *ptr = PyLong_AsVoidPtr(ptr_obj);
645646
if (PyErr_Occurred()) {
646647
return NULL;
647648
}
648649

649-
int res = PyTraceMalloc_Untrack(domain, (uintptr_t)ptr);
650+
int res;
651+
if (release_gil) {
652+
Py_BEGIN_ALLOW_THREADS
653+
res = PyTraceMalloc_Untrack(domain, (uintptr_t)ptr);
654+
Py_END_ALLOW_THREADS
655+
}
656+
else {
657+
res = PyTraceMalloc_Untrack(domain, (uintptr_t)ptr);
658+
}
650659
if (res < 0) {
651660
PyErr_SetString(PyExc_RuntimeError, "PyTraceMalloc_Untrack error");
652661
return NULL;

Modules/_testcapimodule.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3231,6 +3231,7 @@ static void
32313231
tracemalloc_track_race_thread(void *data)
32323232
{
32333233
PyTraceMalloc_Track(123, 10, 1);
3234+
PyTraceMalloc_Untrack(123, 10);
32343235

32353236
PyThread_type_lock lock = (PyThread_type_lock)data;
32363237
PyThread_release_lock(lock);

Python/pylifecycle.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1933,7 +1933,7 @@ Py_FinalizeEx(void)
19331933

19341934
/* Disable tracemalloc after all Python objects have been destroyed,
19351935
so it is possible to use tracemalloc in objects destructor. */
1936-
_PyTraceMalloc_Fini();
1936+
_PyTraceMalloc_Stop();
19371937

19381938
/* Finalize any remaining import state */
19391939
// XXX Move these up to where finalize_modules() is currently.
@@ -1986,6 +1986,8 @@ Py_FinalizeEx(void)
19861986

19871987
finalize_interp_clear(tstate);
19881988

1989+
_PyTraceMalloc_Fini();
1990+
19891991
#ifdef WITH_PYMALLOC
19901992
if (malloc_stats) {
19911993
_PyObject_DebugMallocStats(stderr);

Python/tracemalloc.c

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1313,29 +1313,48 @@ PyTraceMalloc_Track(unsigned int domain, uintptr_t ptr,
13131313
size_t size)
13141314
{
13151315
PyGILState_STATE gil_state = PyGILState_Ensure();
1316+
int result;
1317+
1318+
// gh-129185: Check before TABLES_LOCK() to support calls after
1319+
// _PyTraceMalloc_Fini().
1320+
if (!tracemalloc_config.tracing) {
1321+
result = -2;
1322+
goto done;
1323+
}
1324+
13161325
TABLES_LOCK();
13171326

1318-
int res;
13191327
if (tracemalloc_config.tracing) {
1320-
res = tracemalloc_add_trace(domain, ptr, size);
1328+
result = tracemalloc_add_trace(domain, ptr, size);
13211329
}
13221330
else {
13231331
// gh-128679: tracemalloc.stop() was called by another thread
1324-
res = -2;
1332+
result = -2;
13251333
}
13261334

13271335
TABLES_UNLOCK();
1336+
done:
13281337
PyGILState_Release(gil_state);
1329-
return res;
1338+
return result;
13301339
}
13311340

13321341

13331342
int
13341343
PyTraceMalloc_Untrack(unsigned int domain, uintptr_t ptr)
13351344
{
1345+
// Need the GIL to prevent races on the first 'tracing' test
1346+
PyGILState_STATE gil_state = PyGILState_Ensure();
1347+
int result;
1348+
1349+
// gh-129185: Check before TABLES_LOCK() to support calls after
1350+
// _PyTraceMalloc_Fini()
1351+
if (!tracemalloc_config.tracing) {
1352+
result = -2;
1353+
goto done;
1354+
}
1355+
13361356
TABLES_LOCK();
13371357

1338-
int result;
13391358
if (tracemalloc_config.tracing) {
13401359
tracemalloc_remove_trace(domain, ptr);
13411360
result = 0;
@@ -1346,6 +1365,8 @@ PyTraceMalloc_Untrack(unsigned int domain, uintptr_t ptr)
13461365
}
13471366

13481367
TABLES_UNLOCK();
1368+
done:
1369+
PyGILState_Release(gil_state);
13491370
return result;
13501371
}
13511372

0 commit comments

Comments
 ()
0