Skip to content

Commit 0e6de65

Browse files
committed
Add test
1 parent 59fd8de commit 0e6de65

File tree

2 files changed

+97
-0
lines changed

2 files changed

+97
-0
lines changed

Lib/test/test_tracemalloc.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1101,6 +1101,11 @@ def test_stop_untrack(self):
11011101
with self.assertRaises(RuntimeError):
11021102
self.untrack()
11031103

1104+
@unittest.skipIf(_testcapi is None, 'need _testcapi')
1105+
def test_tracemalloc_track_race(self):
1106+
# gh-128679: Test fix for tracemalloc.stop() race condition
1107+
_testcapi.tracemalloc_track_race()
1108+
11041109

11051110
if __name__ == "__main__":
11061111
unittest.main()

Modules/_testcapimodule.c

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3435,6 +3435,97 @@ code_offset_to_line(PyObject* self, PyObject* const* args, Py_ssize_t nargsf)
34353435
return PyLong_FromInt32(PyCode_Addr2Line(code, offset));
34363436
}
34373437

3438+
3439+
typedef struct {
3440+
PyThread_type_lock lock;
3441+
int completed;
3442+
} tracemalloc_track_race_data;
3443+
3444+
static void
3445+
tracemalloc_track_race_thread(void *raw_data)
3446+
{
3447+
tracemalloc_track_race_data *data = (tracemalloc_track_race_data*)raw_data;
3448+
3449+
PyTraceMalloc_Track(123, 10, 1);
3450+
3451+
PyThread_acquire_lock(data->lock, 1);
3452+
data->completed += 1;
3453+
PyThread_release_lock(data->lock);
3454+
}
3455+
3456+
// gh-128679: Test fix for tracemalloc.stop() race condition
3457+
static PyObject *
3458+
tracemalloc_track_race(PyObject *self, PyObject *args)
3459+
{
3460+
#define NTHREAD 50
3461+
PyObject *stop = NULL;
3462+
tracemalloc_track_race_data data = {0};
3463+
3464+
PyObject *tracemalloc = PyImport_ImportModule("tracemalloc");
3465+
if (tracemalloc == NULL) {
3466+
goto error;
3467+
}
3468+
3469+
PyObject *start = PyObject_GetAttrString(tracemalloc, "start");
3470+
if (start == NULL) {
3471+
goto error;
3472+
}
3473+
PyObject *res = PyObject_CallFunction(start, "i", 1);
3474+
Py_DECREF(start);
3475+
if (res == NULL) {
3476+
goto error;
3477+
}
3478+
3479+
stop = PyObject_GetAttrString(tracemalloc, "stop");
3480+
Py_DECREF(tracemalloc);
3481+
if (stop == NULL) {
3482+
goto error;
3483+
}
3484+
3485+
data.lock = PyThread_allocate_lock();
3486+
if (!data.lock) {
3487+
PyErr_NoMemory();
3488+
goto error;
3489+
}
3490+
3491+
for (size_t i = 0; i < NTHREAD; i++) {
3492+
unsigned long thread = PyThread_start_new_thread(
3493+
tracemalloc_track_race_thread, &data);
3494+
if (thread == (unsigned long)-1) {
3495+
PyErr_SetString(PyExc_RuntimeError, "can't start new thread");
3496+
goto error;
3497+
}
3498+
}
3499+
3500+
res = PyObject_CallNoArgs(stop);
3501+
Py_CLEAR(stop);
3502+
if (res == NULL) {
3503+
goto error;
3504+
}
3505+
3506+
Py_BEGIN_ALLOW_THREADS
3507+
while (1) {
3508+
PyThread_acquire_lock(data.lock, 1);
3509+
int completed = data.completed;
3510+
PyThread_release_lock(data.lock);
3511+
if (completed >= NTHREAD) {
3512+
break;
3513+
}
3514+
sleep(1);
3515+
}
3516+
Py_END_ALLOW_THREADS
3517+
3518+
Py_RETURN_NONE;
3519+
3520+
error:
3521+
Py_CLEAR(stop);
3522+
if (data.lock) {
3523+
PyThread_free_lock(data.lock);
3524+
}
3525+
return NULL;
3526+
#undef NTHREAD
3527+
}
3528+
34383529
static PyMethodDef TestMethods[] = {
34393530
{"set_errno", set_errno, METH_VARARGS},
34403531
{"test_config", test_config, METH_NOARGS},
@@ -3578,6 +3669,7 @@ static PyMethodDef TestMethods[] = {
35783669
{"type_freeze", type_freeze, METH_VARARGS},
35793670
{"test_atexit", test_atexit, METH_NOARGS},
35803671
{"code_offset_to_line", _PyCFunction_CAST(code_offset_to_line), METH_FASTCALL},
3672+
{"tracemalloc_track_race", tracemalloc_track_race, METH_NOARGS},
35813673
{NULL, NULL} /* sentinel */
35823674
};
35833675

0 commit comments

Comments
 (0)