@@ -3238,6 +3238,105 @@ test_atexit(PyObject *self, PyObject *Py_UNUSED(args))
32383238
32393239static PyObject * test_buildvalue_issue38913 (PyObject * , PyObject * );
32403240
3241+
3242+ static void
3243+ tracemalloc_track_race_thread (void * data )
3244+ {
3245+ PyTraceMalloc_Track (123 , 10 , 1 );
3246+
3247+ PyThread_type_lock lock = (PyThread_type_lock )data ;
3248+ PyThread_release_lock (lock );
3249+ }
3250+
3251+ // gh-128679: Test fix for tracemalloc.stop() race condition
3252+ static PyObject *
3253+ tracemalloc_track_race (PyObject * self , PyObject * args )
3254+ {
3255+ #define NTHREAD 50
3256+ PyObject * tracemalloc = NULL ;
3257+ PyObject * stop = NULL ;
3258+ PyThread_type_lock locks [NTHREAD ];
3259+ memset (locks , 0 , sizeof (locks ));
3260+
3261+ // Call tracemalloc.start()
3262+ tracemalloc = PyImport_ImportModule ("tracemalloc" );
3263+ if (tracemalloc == NULL ) {
3264+ goto error ;
3265+ }
3266+ PyObject * start = PyObject_GetAttrString (tracemalloc , "start" );
3267+ if (start == NULL ) {
3268+ goto error ;
3269+ }
3270+ PyObject * res = PyObject_CallNoArgs (start );
3271+ Py_DECREF (start );
3272+ if (res == NULL ) {
3273+ goto error ;
3274+ }
3275+ Py_DECREF (res );
3276+
3277+ stop = PyObject_GetAttrString (tracemalloc , "stop" );
3278+ Py_CLEAR (tracemalloc );
3279+ if (stop == NULL ) {
3280+ goto error ;
3281+ }
3282+
3283+ // Start threads
3284+ for (size_t i = 0 ; i < NTHREAD ; i ++ ) {
3285+ PyThread_type_lock lock = PyThread_allocate_lock ();
3286+ if (!lock ) {
3287+ PyErr_NoMemory ();
3288+ goto error ;
3289+ }
3290+ locks [i ] = lock ;
3291+ PyThread_acquire_lock (lock , 1 );
3292+
3293+ unsigned long thread ;
3294+ thread = PyThread_start_new_thread (tracemalloc_track_race_thread ,
3295+ (void * )lock );
3296+ if (thread == (unsigned long )-1 ) {
3297+ PyErr_SetString (PyExc_RuntimeError , "can't start new thread" );
3298+ goto error ;
3299+ }
3300+ }
3301+
3302+ // Call tracemalloc.stop() while threads are running
3303+ res = PyObject_CallNoArgs (stop );
3304+ Py_CLEAR (stop );
3305+ if (res == NULL ) {
3306+ goto error ;
3307+ }
3308+ Py_DECREF (res );
3309+
3310+ // Wait until threads complete with the GIL released
3311+ Py_BEGIN_ALLOW_THREADS
3312+ for (size_t i = 0 ; i < NTHREAD ; i ++ ) {
3313+ PyThread_type_lock lock = locks [i ];
3314+ PyThread_acquire_lock (lock , 1 );
3315+ PyThread_release_lock (lock );
3316+ }
3317+ Py_END_ALLOW_THREADS
3318+
3319+ // Free threads locks
3320+ for (size_t i = 0 ; i < NTHREAD ; i ++ ) {
3321+ PyThread_type_lock lock = locks [i ];
3322+ PyThread_free_lock (lock );
3323+ }
3324+ Py_RETURN_NONE ;
3325+
3326+ error :
3327+ Py_CLEAR (tracemalloc );
3328+ Py_CLEAR (stop );
3329+ for (size_t i = 0 ; i < NTHREAD ; i ++ ) {
3330+ PyThread_type_lock lock = locks [i ];
3331+ if (lock ) {
3332+ PyThread_free_lock (lock );
3333+ }
3334+ }
3335+ return NULL ;
3336+ #undef NTHREAD
3337+ }
3338+
3339+
32413340static PyMethodDef TestMethods [] = {
32423341 {"set_errno" , set_errno , METH_VARARGS },
32433342 {"test_config" , test_config , METH_NOARGS },
@@ -3378,6 +3477,7 @@ static PyMethodDef TestMethods[] = {
33783477 {"function_get_kw_defaults" , function_get_kw_defaults , METH_O , NULL },
33793478 {"function_set_kw_defaults" , function_set_kw_defaults , METH_VARARGS , NULL },
33803479 {"test_atexit" , test_atexit , METH_NOARGS },
3480+ {"tracemalloc_track_race" , tracemalloc_track_race , METH_NOARGS },
33813481 {NULL , NULL } /* sentinel */
33823482};
33833483
0 commit comments