Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 6b47499

Browse filesBrowse files
authored
[3.13] gh-128679: Fix tracemalloc.stop() race conditions (#128897)
tracemalloc_alloc(), tracemalloc_realloc(), PyTraceMalloc_Track(), PyTraceMalloc_Untrack() and _PyTraceMalloc_TraceRef() now check tracemalloc_config.tracing after calling TABLES_LOCK(). _PyTraceMalloc_Stop() now protects more code with TABLES_LOCK(), especially setting tracemalloc_config.tracing to 1. Add a test using PyTraceMalloc_Track() to test tracemalloc.stop() race condition. Call _PyTraceMalloc_Init() at Python startup.
1 parent ef99618 commit 6b47499
Copy full SHA for 6b47499

File tree

Expand file treeCollapse file tree

7 files changed

+250
-117
lines changed
Filter options
Expand file treeCollapse file tree

7 files changed

+250
-117
lines changed

‎Include/internal/pycore_tracemalloc.h

Copy file name to clipboardExpand all lines: Include/internal/pycore_tracemalloc.h
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ extern PyObject* _PyTraceMalloc_GetTraces(void);
144144
extern PyObject* _PyTraceMalloc_GetObjectTraceback(PyObject *obj);
145145

146146
/* Initialize tracemalloc */
147-
extern int _PyTraceMalloc_Init(void);
147+
extern PyStatus _PyTraceMalloc_Init(void);
148148

149149
/* Start tracemalloc */
150150
extern int _PyTraceMalloc_Start(int max_nframe);

‎Lib/test/test_tracemalloc.py

Copy file name to clipboardExpand all lines: Lib/test/test_tracemalloc.py
+10-2Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
from test.support.script_helper import (assert_python_ok, assert_python_failure,
88
interpreter_requires_environment)
99
from test import support
10-
from test.support import os_helper
1110
from test.support import force_not_colorized
11+
from test.support import os_helper
12+
from test.support import threading_helper
1213

1314
try:
1415
import _testcapi
@@ -952,7 +953,6 @@ def check_env_var_invalid(self, nframe):
952953
return
953954
self.fail(f"unexpected output: {stderr!a}")
954955

955-
956956
def test_env_var_invalid(self):
957957
for nframe in INVALID_NFRAME:
958958
with self.subTest(nframe=nframe):
@@ -1101,6 +1101,14 @@ def test_stop_untrack(self):
11011101
with self.assertRaises(RuntimeError):
11021102
self.untrack()
11031103

1104+
@unittest.skipIf(_testcapi is None, 'need _testcapi')
1105+
@threading_helper.requires_working_threading()
1106+
# gh-128679: Test crash on a debug build (especially on FreeBSD).
1107+
@unittest.skipIf(support.Py_DEBUG, 'need release build')
1108+
def test_tracemalloc_track_race(self):
1109+
# gh-128679: Test fix for tracemalloc.stop() race condition
1110+
_testcapi.tracemalloc_track_race()
1111+
11041112

11051113
if __name__ == "__main__":
11061114
unittest.main()
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :func:`tracemalloc.stop` race condition. Fix :mod:`tracemalloc` to
2+
support calling :func:`tracemalloc.stop` in one thread, while another thread
3+
is tracing memory allocations. Patch by Victor Stinner.

‎Modules/_testcapimodule.c

Copy file name to clipboardExpand all lines: Modules/_testcapimodule.c
+99Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3322,6 +3322,104 @@ test_atexit(PyObject *self, PyObject *Py_UNUSED(args))
33223322
Py_RETURN_NONE;
33233323
}
33243324

3325+
3326+
static void
3327+
tracemalloc_track_race_thread(void *data)
3328+
{
3329+
PyTraceMalloc_Track(123, 10, 1);
3330+
3331+
PyThread_type_lock lock = (PyThread_type_lock)data;
3332+
PyThread_release_lock(lock);
3333+
}
3334+
3335+
// gh-128679: Test fix for tracemalloc.stop() race condition
3336+
static PyObject *
3337+
tracemalloc_track_race(PyObject *self, PyObject *args)
3338+
{
3339+
#define NTHREAD 50
3340+
PyObject *tracemalloc = NULL;
3341+
PyObject *stop = NULL;
3342+
PyThread_type_lock locks[NTHREAD];
3343+
memset(locks, 0, sizeof(locks));
3344+
3345+
// Call tracemalloc.start()
3346+
tracemalloc = PyImport_ImportModule("tracemalloc");
3347+
if (tracemalloc == NULL) {
3348+
goto error;
3349+
}
3350+
PyObject *start = PyObject_GetAttrString(tracemalloc, "start");
3351+
if (start == NULL) {
3352+
goto error;
3353+
}
3354+
PyObject *res = PyObject_CallNoArgs(start);
3355+
Py_DECREF(start);
3356+
if (res == NULL) {
3357+
goto error;
3358+
}
3359+
Py_DECREF(res);
3360+
3361+
stop = PyObject_GetAttrString(tracemalloc, "stop");
3362+
Py_CLEAR(tracemalloc);
3363+
if (stop == NULL) {
3364+
goto error;
3365+
}
3366+
3367+
// Start threads
3368+
for (size_t i = 0; i < NTHREAD; i++) {
3369+
PyThread_type_lock lock = PyThread_allocate_lock();
3370+
if (!lock) {
3371+
PyErr_NoMemory();
3372+
goto error;
3373+
}
3374+
locks[i] = lock;
3375+
PyThread_acquire_lock(lock, 1);
3376+
3377+
unsigned long thread;
3378+
thread = PyThread_start_new_thread(tracemalloc_track_race_thread,
3379+
(void*)lock);
3380+
if (thread == (unsigned long)-1) {
3381+
PyErr_SetString(PyExc_RuntimeError, "can't start new thread");
3382+
goto error;
3383+
}
3384+
}
3385+
3386+
// Call tracemalloc.stop() while threads are running
3387+
res = PyObject_CallNoArgs(stop);
3388+
Py_CLEAR(stop);
3389+
if (res == NULL) {
3390+
goto error;
3391+
}
3392+
Py_DECREF(res);
3393+
3394+
// Wait until threads complete with the GIL released
3395+
Py_BEGIN_ALLOW_THREADS
3396+
for (size_t i = 0; i < NTHREAD; i++) {
3397+
PyThread_type_lock lock = locks[i];
3398+
PyThread_acquire_lock(lock, 1);
3399+
PyThread_release_lock(lock);
3400+
}
3401+
Py_END_ALLOW_THREADS
3402+
3403+
// Free threads locks
3404+
for (size_t i=0; i < NTHREAD; i++) {
3405+
PyThread_type_lock lock = locks[i];
3406+
PyThread_free_lock(lock);
3407+
}
3408+
Py_RETURN_NONE;
3409+
3410+
error:
3411+
Py_CLEAR(tracemalloc);
3412+
Py_CLEAR(stop);
3413+
for (size_t i=0; i < NTHREAD; i++) {
3414+
PyThread_type_lock lock = locks[i];
3415+
if (lock) {
3416+
PyThread_free_lock(lock);
3417+
}
3418+
}
3419+
return NULL;
3420+
#undef NTHREAD
3421+
}
3422+
33253423
static PyMethodDef TestMethods[] = {
33263424
{"set_errno", set_errno, METH_VARARGS},
33273425
{"test_config", test_config, METH_NOARGS},
@@ -3464,6 +3562,7 @@ static PyMethodDef TestMethods[] = {
34643562
{"function_set_warning", function_set_warning, METH_NOARGS},
34653563
{"test_critical_sections", test_critical_sections, METH_NOARGS},
34663564
{"test_atexit", test_atexit, METH_NOARGS},
3565+
{"tracemalloc_track_race", tracemalloc_track_race, METH_NOARGS},
34673566
{NULL, NULL} /* sentinel */
34683567
};
34693568

‎Modules/_tracemalloc.c

Copy file name to clipboardExpand all lines: Modules/_tracemalloc.c
-5Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -223,10 +223,5 @@ PyInit__tracemalloc(void)
223223
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
224224
#endif
225225

226-
if (_PyTraceMalloc_Init() < 0) {
227-
Py_DECREF(m);
228-
return NULL;
229-
}
230-
231226
return m;
232227
}

‎Python/pylifecycle.c

Copy file name to clipboardExpand all lines: Python/pylifecycle.c
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,11 @@ pycore_create_interpreter(_PyRuntimeState *runtime,
706706
return _PyStatus_NO_MEMORY();
707707
}
708708

709+
status = _PyTraceMalloc_Init();
710+
if (_PyStatus_EXCEPTION(status)) {
711+
return status;
712+
}
713+
709714
PyThreadState *tstate = _PyThreadState_New(interp,
710715
_PyThreadState_WHENCE_INIT);
711716
if (tstate == NULL) {

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.