@@ -538,12 +538,16 @@ tracemalloc_alloc(int use_calloc, void *ctx, size_t nelem, size_t elsize)
538538 return NULL ;
539539
540540 TABLES_LOCK ();
541- if (ADD_TRACE (ptr , nelem * elsize ) < 0 ) {
542- /* Failed to allocate a trace for the new memory block */
543- TABLES_UNLOCK ();
544- alloc -> free (alloc -> ctx , ptr );
545- return NULL ;
541+
542+ if (tracemalloc_config .tracing ) {
543+ if (ADD_TRACE (ptr , nelem * elsize ) < 0 ) {
544+ /* Failed to allocate a trace for the new memory block */
545+ alloc -> free (alloc -> ctx , ptr );
546+ ptr = NULL ;
547+ }
546548 }
549+ // else: gh-128679: tracemalloc.stop() was called by another thread
550+
547551 TABLES_UNLOCK ();
548552 return ptr ;
549553}
@@ -559,11 +563,15 @@ tracemalloc_realloc(void *ctx, void *ptr, size_t new_size)
559563 if (ptr2 == NULL )
560564 return NULL ;
561565
566+ TABLES_LOCK ();
567+ if (!tracemalloc_config .tracing ) {
568+ // gh-128679: tracemalloc.stop() was called by another thread
569+ goto done ;
570+ }
571+
562572 if (ptr != NULL ) {
563573 /* an existing memory block has been resized */
564574
565- TABLES_LOCK ();
566-
567575 /* tracemalloc_add_trace() updates the trace if there is already
568576 a trace at address ptr2 */
569577 if (ptr2 != ptr ) {
@@ -576,45 +584,46 @@ tracemalloc_realloc(void *ctx, void *ptr, size_t new_size)
576584 memory block and so removed bytes.
577585
578586 This case is very unlikely: a hash entry has just been
579- released, so the hash table should have at least one free entry.
587+ released, so the hash table should have at least one free
588+ entry.
580589
581590 The GIL and the table lock ensures that only one thread is
582591 allocating memory. */
583592 Py_FatalError ("tracemalloc_realloc() failed to allocate a trace" );
584593 }
585- TABLES_UNLOCK ();
586594 }
587595 else {
588596 /* new allocation */
589597
590- TABLES_LOCK ();
591598 if (ADD_TRACE (ptr2 , new_size ) < 0 ) {
592599 /* Failed to allocate a trace for the new memory block */
593- TABLES_UNLOCK ();
594600 alloc -> free (alloc -> ctx , ptr2 );
595- return NULL ;
601+ ptr2 = NULL ;
596602 }
597- TABLES_UNLOCK ();
598603 }
604+
605+ done :
606+ TABLES_UNLOCK ();
599607 return ptr2 ;
600608}
601609
602610
603611static void
604612tracemalloc_free (void * ctx , void * ptr )
605613{
606- PyMemAllocatorEx * alloc = (PyMemAllocatorEx * )ctx ;
607-
608614 if (ptr == NULL )
609615 return ;
610616
611- /* GIL cannot be locked in PyMem_RawFree() because it would introduce
612- a deadlock in _PyThreadState_DeleteCurrent(). */
613-
617+ PyMemAllocatorEx * alloc = (PyMemAllocatorEx * )ctx ;
614618 alloc -> free (alloc -> ctx , ptr );
615619
616620 TABLES_LOCK ();
617- REMOVE_TRACE (ptr );
621+
622+ if (tracemalloc_config .tracing ) {
623+ REMOVE_TRACE (ptr );
624+ }
625+ // else: gh-128679: tracemalloc.stop() was called by another thread
626+
618627 TABLES_UNLOCK ();
619628}
620629
@@ -779,17 +788,15 @@ tracemalloc_clear_filename(void *value)
779788
780789/* reentrant flag must be set to call this function and GIL must be held */
781790static void
782- tracemalloc_clear_traces (void )
791+ tracemalloc_clear_traces_unlocked (void )
783792{
784793 /* The GIL protects variables against concurrent access */
785794 assert (PyGILState_Check ());
786795
787- TABLES_LOCK ();
788796 _Py_hashtable_clear (tracemalloc_traces );
789797 _Py_hashtable_clear (tracemalloc_domains );
790798 tracemalloc_traced_memory = 0 ;
791799 tracemalloc_peak_traced_memory = 0 ;
792- TABLES_UNLOCK ();
793800
794801 _Py_hashtable_clear (tracemalloc_tracebacks );
795802
@@ -963,6 +970,10 @@ _PyTraceMalloc_Stop(void)
963970 if (!tracemalloc_config .tracing )
964971 return ;
965972
973+ // Lock to synchronize with tracemalloc_free() which checks
974+ // 'tracing' while holding the lock.
975+ TABLES_LOCK ();
976+
966977 /* stop tracing Python memory allocations */
967978 tracemalloc_config .tracing = 0 ;
968979
@@ -973,11 +984,13 @@ _PyTraceMalloc_Stop(void)
973984 PyMem_SetAllocator (PYMEM_DOMAIN_MEM , & allocators .mem );
974985 PyMem_SetAllocator (PYMEM_DOMAIN_OBJ , & allocators .obj );
975986
976- tracemalloc_clear_traces ();
987+ tracemalloc_clear_traces_unlocked ();
977988
978989 /* release memory */
979990 raw_free (tracemalloc_traceback );
980991 tracemalloc_traceback = NULL ;
992+
993+ TABLES_UNLOCK ();
981994}
982995
983996
@@ -1307,20 +1320,19 @@ int
13071320PyTraceMalloc_Track (unsigned int domain , uintptr_t ptr ,
13081321 size_t size )
13091322{
1310- int res ;
1311- PyGILState_STATE gil_state ;
1323+ PyGILState_STATE gil_state = PyGILState_Ensure () ;
1324+ TABLES_LOCK () ;
13121325
1313- if (!tracemalloc_config .tracing ) {
1314- /* tracemalloc is not tracing: do nothing */
1315- return -2 ;
1326+ int res ;
1327+ if (tracemalloc_config .tracing ) {
1328+ res = tracemalloc_add_trace (domain , ptr , size );
1329+ }
1330+ else {
1331+ // gh-128679: tracemalloc.stop() was called by another thread
1332+ res = -2 ;
13161333 }
13171334
1318- gil_state = PyGILState_Ensure ();
1319-
1320- TABLES_LOCK ();
1321- res = tracemalloc_add_trace (domain , ptr , size );
13221335 TABLES_UNLOCK ();
1323-
13241336 PyGILState_Release (gil_state );
13251337 return res ;
13261338}
@@ -1329,16 +1341,20 @@ PyTraceMalloc_Track(unsigned int domain, uintptr_t ptr,
13291341int
13301342PyTraceMalloc_Untrack (unsigned int domain , uintptr_t ptr )
13311343{
1332- if (!tracemalloc_config .tracing ) {
1344+ TABLES_LOCK ();
1345+
1346+ int result ;
1347+ if (tracemalloc_config .tracing ) {
1348+ tracemalloc_remove_trace (domain , ptr );
1349+ result = 0 ;
1350+ }
1351+ else {
13331352 /* tracemalloc is not tracing: do nothing */
1334- return -2 ;
1353+ result = -2 ;
13351354 }
13361355
1337- TABLES_LOCK ();
1338- tracemalloc_remove_trace (domain , ptr );
13391356 TABLES_UNLOCK ();
1340-
1341- return 0 ;
1357+ return result ;
13421358}
13431359
13441360
@@ -1376,16 +1392,21 @@ _PyTraceMalloc_TraceRef(PyObject *op, PyRefTracerEvent event, void* Py_UNUSED(ig
13761392 int res = -1 ;
13771393
13781394 TABLES_LOCK ();
1379- trace_t * trace = _Py_hashtable_get (tracemalloc_traces , TO_PTR (ptr ));
1380- if (trace != NULL ) {
1381- /* update the traceback of the memory block */
1382- traceback_t * traceback = traceback_new ();
1383- if (traceback != NULL ) {
1384- trace -> traceback = traceback ;
1385- res = 0 ;
1395+
1396+ if (tracemalloc_config .tracing ) {
1397+ trace_t * trace = _Py_hashtable_get (tracemalloc_traces , TO_PTR (ptr ));
1398+ if (trace != NULL ) {
1399+ /* update the traceback of the memory block */
1400+ traceback_t * traceback = traceback_new ();
1401+ if (traceback != NULL ) {
1402+ trace -> traceback = traceback ;
1403+ res = 0 ;
1404+ }
13861405 }
1406+ /* else: cannot track the object, its memory block size is unknown */
13871407 }
1388- /* else: cannot track the object, its memory block size is unknown */
1408+ // else: gh-128679: tracemalloc.stop() was called by another thread
1409+
13891410 TABLES_UNLOCK ();
13901411
13911412 return res ;
@@ -1418,7 +1439,9 @@ _PyTraceMalloc_ClearTraces(void)
14181439 return ;
14191440 }
14201441 set_reentrant (1 );
1421- tracemalloc_clear_traces ();
1442+ TABLES_LOCK ();
1443+ tracemalloc_clear_traces_unlocked ();
1444+ TABLES_UNLOCK ();
14221445 set_reentrant (0 );
14231446}
14241447
0 commit comments