@@ -42,47 +42,95 @@ static PyObject* object_string = NULL;
42
42
43
43
#define ALLOC_TRACKER_MAX_COUNT UINT64_MAX
44
44
45
+ // The data coordination primitives in this and related files are related to a crash we started seeing.
46
+ // We don't have a precise understanding of the causal factors within the runtime that lead to this condition,
47
+ // since the GIL alone was sufficient in the past for preventing this issue.
48
+ // We add an option here to _add_ a crash, in order to observe this condition in a future diagnostic iteration.
49
+ // **This option is _intended_ to crash the Python process** do not use without a good reason!
50
+ static char g_crash_on_mutex_pass_str [] = "_DD_PROFILING_MEMALLOC_CRASH_ON_MUTEX_PASS" ;
51
+ static const char * g_truthy_values [] = { "1" , "true" , "yes" , "on" , "enable" , "enabled" , NULL }; // NB the sentinel NULL
52
+ static memlock_t g_memalloc_lock ;
53
+
45
54
static alloc_tracker_t * global_alloc_tracker ;
46
55
56
+ // This is a multiplatform way to define an operation to happen at static initialization time
57
+ static void
58
+ memalloc_init (void );
59
+
60
+ #ifdef _MSC_VER
61
+ #pragma section(".CRT$XCU", read)
62
+ __declspec(allocate (".CRT$XCU" )) void (* memalloc_init_func )(void ) = memalloc_init ;
63
+
64
+ #elif defined(__GNUC__ ) || defined(__clang__ )
65
+ __attribute__((constructor ))
66
+ #else
67
+ #error Unsupported compiler
68
+ #endif
69
+ static void
70
+ memalloc_init ()
71
+ {
72
+ // Check if we should crash the process on mutex pass
73
+ char * crash_on_mutex_pass_str = getenv (g_crash_on_mutex_pass_str );
74
+ bool crash_on_mutex_pass = false;
75
+ if (crash_on_mutex_pass_str ) {
76
+ for (int i = 0 ; g_truthy_values [i ]; i ++ ) {
77
+ if (strcmp (crash_on_mutex_pass_str , g_truthy_values [i ]) == 0 ) {
78
+ crash_on_mutex_pass = true;
79
+ break ;
80
+ }
81
+ }
82
+ }
83
+ memlock_init (& g_memalloc_lock , crash_on_mutex_pass );
84
+ }
85
+
47
86
static void
48
87
memalloc_add_event (memalloc_context_t * ctx , void * ptr , size_t size )
49
88
{
50
- /* Do not overflow; just ignore the new events if we ever reach that point */
51
- if (global_alloc_tracker -> alloc_count >= ALLOC_TRACKER_MAX_COUNT )
89
+ uint64_t alloc_count = atomic_add_clamped (& global_alloc_tracker -> alloc_count , 1 , ALLOC_TRACKER_MAX_COUNT );
90
+
91
+ /* Return if we've reached the maximum number of allocations */
92
+ if (alloc_count == 0 )
52
93
return ;
53
94
54
- global_alloc_tracker -> alloc_count ++ ;
95
+ // Return if we can't take the guard
96
+ if (!memalloc_take_guard ()) {
97
+ return ;
98
+ }
55
99
56
- /* Avoid loops */
57
- if (memalloc_get_reentrant ())
100
+ // In this implementation, the `global_alloc_tracker` isn't intrinsically protected. Before we read or modify,
101
+ // take the lock. The count of allocations is already forward-attributed elsewhere, so if we can't take the lock
102
+ // there's nothing to do.
103
+ if (!memlock_trylock (& g_memalloc_lock )) {
58
104
return ;
105
+ }
59
106
60
107
/* Determine if we can capture or if we need to sample */
61
108
if (global_alloc_tracker -> allocs .count < ctx -> max_events ) {
62
- /* set a barrier so we don't loop as getting a traceback allocates memory */
63
- memalloc_set_reentrant (true);
64
109
/* Buffer is not full, fill it */
65
110
traceback_t * tb = memalloc_get_traceback (ctx -> max_nframe , ptr , size , ctx -> domain );
66
- memalloc_set_reentrant (false);
67
- if (tb )
111
+ if (tb ) {
68
112
traceback_array_append (& global_alloc_tracker -> allocs , tb );
113
+ }
69
114
} else {
70
115
/* Sampling mode using a reservoir sampling algorithm: replace a random
71
116
* traceback with this one */
72
- uint64_t r = random_range (global_alloc_tracker -> alloc_count );
117
+ uint64_t r = random_range (alloc_count );
73
118
74
- if (r < ctx -> max_events ) {
75
- /* set a barrier so we don't loop as getting a traceback allocates memory */
76
- memalloc_set_reentrant (true);
119
+ // In addition to event size, need to check that the tab is in a good state
120
+ if (r < ctx -> max_events && global_alloc_tracker -> allocs .tab != NULL ) {
77
121
/* Replace a random traceback with this one */
78
122
traceback_t * tb = memalloc_get_traceback (ctx -> max_nframe , ptr , size , ctx -> domain );
79
- memalloc_set_reentrant (false);
123
+
124
+ // Need to check not only that the tb returned
80
125
if (tb ) {
81
126
traceback_free (global_alloc_tracker -> allocs .tab [r ]);
82
127
global_alloc_tracker -> allocs .tab [r ] = tb ;
83
128
}
84
129
}
85
130
}
131
+
132
+ memlock_unlock (& g_memalloc_lock );
133
+ memalloc_yield_guard ();
86
134
}
87
135
88
136
static void
@@ -98,12 +146,6 @@ memalloc_free(void* ctx, void* ptr)
98
146
alloc -> free (alloc -> ctx , ptr );
99
147
}
100
148
101
- #ifdef _PY37_AND_LATER
102
- Py_tss_t memalloc_reentrant_key = Py_tss_NEEDS_INIT ;
103
- #else
104
- int memalloc_reentrant_key = -1 ;
105
- #endif
106
-
107
149
static void *
108
150
memalloc_alloc (int use_calloc , void * ctx , size_t nelem , size_t elsize )
109
151
{
@@ -233,7 +275,10 @@ memalloc_start(PyObject* Py_UNUSED(module), PyObject* args)
233
275
234
276
global_memalloc_ctx .domain = PYMEM_DOMAIN_OBJ ;
235
277
236
- global_alloc_tracker = alloc_tracker_new ();
278
+ if (memlock_trylock (& g_memalloc_lock )) {
279
+ global_alloc_tracker = alloc_tracker_new ();
280
+ memlock_unlock (& g_memalloc_lock );
281
+ }
237
282
238
283
PyMem_GetAllocator (PYMEM_DOMAIN_OBJ , & global_memalloc_ctx .pymem_allocator_obj );
239
284
PyMem_SetAllocator (PYMEM_DOMAIN_OBJ , & alloc );
@@ -258,8 +303,11 @@ memalloc_stop(PyObject* Py_UNUSED(module), PyObject* Py_UNUSED(args))
258
303
259
304
PyMem_SetAllocator (PYMEM_DOMAIN_OBJ , & global_memalloc_ctx .pymem_allocator_obj );
260
305
memalloc_tb_deinit ();
261
- alloc_tracker_free (global_alloc_tracker );
262
- global_alloc_tracker = NULL ;
306
+ if (memlock_trylock (& g_memalloc_lock )) {
307
+ alloc_tracker_free (global_alloc_tracker );
308
+ global_alloc_tracker = NULL ;
309
+ memlock_unlock (& g_memalloc_lock );
310
+ }
263
311
264
312
memalloc_heap_tracker_deinit ();
265
313
@@ -310,9 +358,15 @@ iterevents_new(PyTypeObject* type, PyObject* Py_UNUSED(args), PyObject* Py_UNUSE
310
358
if (!iestate )
311
359
return NULL ;
312
360
313
- iestate -> alloc_tracker = global_alloc_tracker ;
314
361
/* reset the current traceback list */
315
- global_alloc_tracker = alloc_tracker_new ();
362
+ if (memlock_trylock (& g_memalloc_lock )) {
363
+ iestate -> alloc_tracker = global_alloc_tracker ;
364
+ global_alloc_tracker = alloc_tracker_new ();
365
+ memlock_unlock (& g_memalloc_lock );
366
+ } else {
367
+ Py_TYPE (iestate )-> tp_free (iestate );
368
+ return NULL ;
369
+ }
316
370
iestate -> seq_index = 0 ;
317
371
318
372
PyObject * iter_and_count = PyTuple_New (3 );
@@ -326,8 +380,11 @@ iterevents_new(PyTypeObject* type, PyObject* Py_UNUSED(args), PyObject* Py_UNUSE
326
380
static void
327
381
iterevents_dealloc (IterEventsState * iestate )
328
382
{
329
- alloc_tracker_free (iestate -> alloc_tracker );
330
- Py_TYPE (iestate )-> tp_free (iestate );
383
+ if (memlock_trylock (& g_memalloc_lock )) {
384
+ alloc_tracker_free (iestate -> alloc_tracker );
385
+ Py_TYPE (iestate )-> tp_free (iestate );
386
+ memlock_unlock (& g_memalloc_lock );
387
+ }
331
388
}
332
389
333
390
static PyObject *
@@ -442,20 +499,6 @@ PyInit__memalloc(void)
442
499
return NULL ;
443
500
}
444
501
445
- #ifdef _PY37_AND_LATER
446
- if (PyThread_tss_create (& memalloc_reentrant_key ) != 0 ) {
447
- #else
448
- memalloc_reentrant_key = PyThread_create_key ();
449
- if (memalloc_reentrant_key == -1 ) {
450
- #endif
451
- #ifdef MS_WINDOWS
452
- PyErr_SetFromWindowsErr (0 );
453
- #else
454
- PyErr_SetFromErrno (PyExc_OSError );
455
- #endif
456
- return NULL ;
457
- }
458
-
459
502
if (PyType_Ready (& MemallocIterEvents_Type ) < 0 )
460
503
return NULL ;
461
504
Py_INCREF ((PyObject * )& MemallocIterEvents_Type );
0 commit comments