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

Skip to content

Commit 46c7e13

Browse files
authored
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().
1 parent 225296c commit 46c7e13

File tree

5 files changed

+75
-11
lines changed

5 files changed

+75
-11
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
@@ -19,6 +20,7 @@
1920
_testinternalcapi = None
2021

2122

23+
DEFAULT_DOMAIN = 0
2224
EMPTY_STRING_SIZE = sys.getsizeof(b'')
2325
INVALID_NFRAME = (-1, 2**30)
2426

@@ -1027,8 +1029,8 @@ def track(self, release_gil=False, nframe=1):
10271029
release_gil)
10281030
return frames
10291031

1030-
def untrack(self):
1031-
_testcapi.tracemalloc_untrack(self.domain, self.ptr)
1032+
def untrack(self, release_gil=False):
1033+
_testcapi.tracemalloc_untrack(self.domain, self.ptr, release_gil)
10321034

10331035
def get_traced_memory(self):
10341036
# Get the traced size in the domain
@@ -1070,21 +1072,27 @@ def test_track_already_tracked(self):
10701072
self.assertEqual(self.get_traceback(),
10711073
tracemalloc.Traceback(frames))
10721074

1073-
def test_untrack(self):
1075+
def check_untrack(self, release_gil):
10741076
tracemalloc.start()
10751077

10761078
self.track()
10771079
self.assertIsNotNone(self.get_traceback())
10781080
self.assertEqual(self.get_traced_memory(), self.size)
10791081

10801082
# untrack must remove the trace
1081-
self.untrack()
1083+
self.untrack(release_gil)
10821084
self.assertIsNone(self.get_traceback())
10831085
self.assertEqual(self.get_traced_memory(), 0)
10841086

10851087
# calling _PyTraceMalloc_Untrack() multiple times must not crash
1086-
self.untrack()
1087-
self.untrack()
1088+
self.untrack(release_gil)
1089+
self.untrack(release_gil)
1090+
1091+
def test_untrack(self):
1092+
self.check_untrack(False)
1093+
1094+
def test_untrack_without_gil(self):
1095+
self.check_untrack(True)
10881096

10891097
def test_stop_track(self):
10901098
tracemalloc.start()
@@ -1110,6 +1118,29 @@ def test_tracemalloc_track_race(self):
11101118
# gh-128679: Test fix for tracemalloc.stop() race condition
11111119
_testcapi.tracemalloc_track_race()
11121120

1121+
def test_late_untrack(self):
1122+
code = textwrap.dedent(f"""
1123+
from test import support
1124+
import tracemalloc
1125+
import _testcapi
1126+
1127+
class Tracked:
1128+
def __init__(self, domain, size):
1129+
self.domain = domain
1130+
self.ptr = id(self)
1131+
self.size = size
1132+
_testcapi.tracemalloc_track(self.domain, self.ptr, self.size)
1133+
1134+
def __del__(self, untrack=_testcapi.tracemalloc_untrack):
1135+
untrack(self.domain, self.ptr, 1)
1136+
1137+
domain = {DEFAULT_DOMAIN}
1138+
tracemalloc.start()
1139+
obj = Tracked(domain, 1024 * 1024)
1140+
support.late_deletion(obj)
1141+
""")
1142+
assert_python_ok("-c", code)
1143+
11131144

11141145
if __name__ == "__main__":
11151146
unittest.main()

Modules/_testcapi/mem.c

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -557,16 +557,25 @@ tracemalloc_untrack(PyObject *self, PyObject *args)
557557
{
558558
unsigned int domain;
559559
PyObject *ptr_obj;
560+
int release_gil = 0;
560561

561-
if (!PyArg_ParseTuple(args, "IO", &domain, &ptr_obj)) {
562+
if (!PyArg_ParseTuple(args, "IO|i", &domain, &ptr_obj, &release_gil)) {
562563
return NULL;
563564
}
564565
void *ptr = PyLong_AsVoidPtr(ptr_obj);
565566
if (PyErr_Occurred()) {
566567
return NULL;
567568
}
568569

569-
int res = PyTraceMalloc_Untrack(domain, (uintptr_t)ptr);
570+
int res;
571+
if (release_gil) {
572+
Py_BEGIN_ALLOW_THREADS
573+
res = PyTraceMalloc_Untrack(domain, (uintptr_t)ptr);
574+
Py_END_ALLOW_THREADS
575+
}
576+
else {
577+
res = PyTraceMalloc_Untrack(domain, (uintptr_t)ptr);
578+
}
570579
if (res < 0) {
571580
PyErr_SetString(PyExc_RuntimeError, "PyTraceMalloc_Untrack error");
572581
return NULL;

Modules/_testcapimodule.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3394,6 +3394,7 @@ static void
33943394
tracemalloc_track_race_thread(void *data)
33953395
{
33963396
PyTraceMalloc_Track(123, 10, 1);
3397+
PyTraceMalloc_Untrack(123, 10);
33973398

33983399
PyThread_type_lock lock = (PyThread_type_lock)data;
33993400
PyThread_release_lock(lock);

Python/pylifecycle.c

Lines changed: 3 additions & 1 deletion
< 10000 td data-grid-cell-id="diff-69223f5bb01f359a23246afbbf75b45c622e719424418d658674fbb6c2b36fad-2168-2170-1" data-selected="false" role="gridcell" style="background-color:var(--diffBlob-additionNum-bgColor, var(--diffBlob-addition-bgColor-num));text-align:center" tabindex="-1" valign="top" class="focusable-grid-cell diff-line-number position-relative left-side">2170
Original file line numberDiff line numberDiff line change
@@ -2113,7 +2113,7 @@ _Py_Finalize(_PyRuntimeState *runtime)
21132113

21142114
/* Disable tracemalloc after all Python objects have been destroyed,
21152115
so it is possible to use tracemalloc in objects destructor. */
2116-
_PyTraceMalloc_Fini();
2116+
_PyTraceMalloc_Stop();
21172117

21182118
/* Finalize any remaining import state */
21192119
// XXX Move these up to where finalize_modules() is currently.
@@ -2166,6 +2166,8 @@ _Py_Finalize(_PyRuntimeState *runtime)
21662166

21672167
finalize_interp_clear(tstate);
21682168

2169+
_PyTraceMalloc_Fini();
+
21692171
#ifdef Py_TRACE_REFS
21702172
/* Display addresses (& refcnts) of all objects still alive.
21712173
* An address can be used to find the repr of the object, printed

Python/tracemalloc.c

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1256,9 +1256,17 @@ PyTraceMalloc_Track(unsigned int domain, uintptr_t ptr,
12561256
size_t size)
12571257
{
12581258
PyGILState_STATE gil_state = PyGILState_Ensure();
1259+
int result;
1260+
1261+
// gh-129185: Check before TABLES_LOCK() to support calls after
1262+
// _PyTraceMalloc_Fini().
1263+
if (!tracemalloc_config.tracing) {
1264+
result = -2;
1265+
goto done;
1266+
}
1267+
12591268
TABLES_LOCK();
12601269

1261-
int result;
12621270
if (tracemalloc_config.tracing) {
12631271
result = tracemalloc_add_trace_unlocked(domain, ptr, size);
12641272
}
@@ -1268,6 +1276,7 @@ PyTraceMalloc_Track(unsigned int domain, uintptr_t ptr,
12681276
}
12691277

12701278
TABLES_UNLOCK();
1279+
done:
12711280
PyGILState_Release(gil_state);
12721281

12731282
return result;
@@ -1277,9 +1286,19 @@ PyTraceMalloc_Track(unsigned int domain, uintptr_t ptr,
12771286
int
12781287
PyTraceMalloc_Untrack(unsigned int domain, uintptr_t ptr)
12791288
{
1289+
// Need the GIL to prevent races on the first 'tracing' test
1290+
PyGILState_STATE gil_state = PyGILState_Ensure();
1291+
int result;
1292+
1293+
// gh-129185: Check before TABLES_LOCK() to support calls after
1294+
// _PyTraceMalloc_Fini()
1295+
if (!tracemalloc_config.tracing) {
1296+
result = -2;
1297+
goto done;
1298+
}
1299+
12801300
TABLES_LOCK();
12811301

1282-
int result;
12831302
if (tracemalloc_config.tracing) {
12841303
tracemalloc_remove_trace_unlocked(domain, ptr);
12851304
result = 0;
@@ -1290,6 +1309,8 @@ PyTraceMalloc_Untrack(unsigned int domain, uintptr_t ptr)
12901309
}
12911310

12921311
TABLES_UNLOCK();
1312+
done:
1313+
PyGILState_Release(gil_state);
12931314
return result;
12941315
}
12951316

0 commit comments

Comments
 (0)
0