Skip to content

Commit ca22147

Browse files
authored
gh-111924: Fix data races when swapping allocators (gh-130287)
CPython current temporarily changes `PYMEM_DOMAIN_RAW` to the default allocator during initialization and shutdown. The motivation is to ensure that core runtime structures are allocated and freed using the same allocator. However, modifying the current allocator changes global state and is not thread-safe even with the GIL. Other threads may be allocating or freeing objects use PYMEM_DOMAIN_RAW; they are not required to hold the GIL to call PyMem_RawMalloc/PyMem_RawFree. This adds new internal-only functions like `_PyMem_DefaultRawMalloc` that aren't affected by calls to `PyMem_SetAllocator()`, so they're appropriate for Python runtime initialization and finalization. Use these calls in places where we previously swapped to the default raw allocator.
1 parent 568db40 commit ca22147

File tree

9 files changed

+155
-154
lines changed

9 files changed

+155
-154
lines changed

Include/internal/pycore_pymem.h

+7-8
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,6 @@ struct _Py_mem_interp_free_queue {
5555
struct llist_node head; // queue of _mem_work_chunk items
5656
};
5757

58-
/* Set the memory allocator of the specified domain to the default.
59-
Save the old allocator into *old_alloc if it's non-NULL.
60-
Return on success, or return -1 if the domain is unknown. */
61-
extern int _PyMem_SetDefaultAllocator(
62-
PyMemAllocatorDomain domain,
63-
PyMemAllocatorEx *old_alloc);
64-
6558
/* Special bytes broadcast into debug memory blocks at appropriate times.
6659
Strings of these are unlikely to be valid addresses, floats, ints or
6760
7-bit ASCII.
@@ -113,6 +106,13 @@ extern int _PyMem_GetAllocatorName(
113106
PYMEM_ALLOCATOR_NOT_SET does nothing. */
114107
extern int _PyMem_SetupAllocators(PyMemAllocatorName allocator);
115108

109+
// Default raw memory allocator that is not affected by PyMem_SetAllocator()
110+
extern void *_PyMem_DefaultRawMalloc(size_t);
111+
extern void *_PyMem_DefaultRawCalloc(size_t, size_t);
112+
extern void *_PyMem_DefaultRawRealloc(void *, size_t);
113+
extern void _PyMem_DefaultRawFree(void *);
114+
extern wchar_t *_PyMem_DefaultRawWcsdup(const wchar_t *str);
115+
116116
/* Is the debug allocator enabled? */
117117
extern int _PyMem_DebugEnabled(void);
118118

@@ -132,7 +132,6 @@ static inline void _PyObject_XDecRefDelayed(PyObject *obj)
132132
// Periodically process delayed free requests.
133133
extern void _PyMem_ProcessDelayed(PyThreadState *tstate);
134134

135-
136135
// Periodically process delayed free requests when the world is stopped.
137136
// Notify of any objects whic should be freeed.
138137
typedef void (*delayed_dealloc_cb)(PyObject *, void *);

Objects/obmalloc.c

+62-11
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,68 @@ void _PyMem_DebugFree(void *ctx, void *p);
344344
#define PYDBGOBJ_ALLOC \
345345
{&_PyRuntime.allocators.debug.obj, _PyMem_DebugMalloc, _PyMem_DebugCalloc, _PyMem_DebugRealloc, _PyMem_DebugFree}
346346

347+
/* default raw allocator (not swappable) */
348+
349+
void *
350+
_PyMem_DefaultRawMalloc(size_t size)
351+
{
352+
#ifdef Py_DEBUG
353+
return _PyMem_DebugRawMalloc(&_PyRuntime.allocators.debug.raw, size);
354+
#else
355+
return _PyMem_RawMalloc(NULL, size);
356+
#endif
357+
}
358+
359+
void *
360+
_PyMem_DefaultRawCalloc(size_t nelem, size_t elsize)
361+
{
362+
#ifdef Py_DEBUG
363+
return _PyMem_DebugRawCalloc(&_PyRuntime.allocators.debug.raw, nelem, elsize);
364+
#else
365+
return _PyMem_RawCalloc(NULL, nelem, elsize);
366+
#endif
367+
}
368+
369+
void *
370+
_PyMem_DefaultRawRealloc(void *ptr, size_t size)
371+
{
372+
#ifdef Py_DEBUG
373+
return _PyMem_DebugRawRealloc(&_PyRuntime.allocators.debug.raw, ptr, size);
374+
#else
375+
return _PyMem_RawRealloc(NULL, ptr, size);
376+
#endif
377+
}
378+
379+
void
380+
_PyMem_DefaultRawFree(void *ptr)
381+
{
382+
#ifdef Py_DEBUG
383+
_PyMem_DebugRawFree(&_PyRuntime.allocators.debug.raw, ptr);
384+
#else
385+
_PyMem_RawFree(NULL, ptr);
386+
#endif
387+
}
388+
389+
wchar_t*
390+
_PyMem_DefaultRawWcsdup(const wchar_t *str)
391+
{
392+
assert(str != NULL);
393+
394+
size_t len = wcslen(str);
395+
if (len > (size_t)PY_SSIZE_T_MAX / sizeof(wchar_t) - 1) {
396+
return NULL;
397+
}
398+
399+
size_t size = (len + 1) * sizeof(wchar_t);
400+
wchar_t *str2 = _PyMem_DefaultRawMalloc(size);
401+
if (str2 == NULL) {
402+
return NULL;
403+
}
404+
405+
memcpy(str2, str, size);
406+
return str2;
407+
}
408+
347409
/* the low-level virtual memory allocator */
348410

349411
#ifdef WITH_PYMALLOC
@@ -492,17 +554,6 @@ static const int pydebug = 1;
492554
static const int pydebug = 0;
493555
#endif
494556

495-
int
496-
_PyMem_SetDefaultAllocator(PyMemAllocatorDomain domain,
497-
PyMemAllocatorEx *old_alloc)
498-
{
499-
PyMutex_Lock(&ALLOCATORS_MUTEX);
500-
int res = set_default_allocator_unlocked(domain, pydebug, old_alloc);
501-
PyMutex_Unlock(&ALLOCATORS_MUTEX);
502-
return res;
503-
}
504-
505-
506557
int
507558
_PyMem_GetAllocatorName(const char *name, PyMemAllocatorName *allocator)
508559
{

Python/import.c

+7-36
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
#include "pycore_pyerrors.h" // _PyErr_SetString()
1414
#include "pycore_pyhash.h" // _Py_KeyedHash()
1515
#include "pycore_pylifecycle.h"
16-
#include "pycore_pymem.h" // _PyMem_SetDefaultAllocator()
16+
#include "pycore_pymem.h" // _PyMem_DefaultRawFree()
1717
#include "pycore_pystate.h" // _PyInterpreterState_GET()
1818
#include "pycore_sysmodule.h" // _PySys_ClearAttrString()
1919
#include "pycore_time.h" // _PyTime_AsMicroseconds()
@@ -2387,14 +2387,11 @@ PyImport_ExtendInittab(struct _inittab *newtab)
23872387

23882388
/* Force default raw memory allocator to get a known allocator to be able
23892389
to release the memory in _PyImport_Fini2() */
2390-
PyMemAllocatorEx old_alloc;
2391-
_PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
2392-
23932390
/* Allocate new memory for the combined table */
23942391
p = NULL;
23952392
if (i + n <= SIZE_MAX / sizeof(struct _inittab) - 1) {
23962393
size_t size = sizeof(struct _inittab) * (i + n + 1);
2397-
p = PyMem_RawRealloc(inittab_copy, size);
2394+
p = _PyMem_DefaultRawRealloc(inittab_copy, size);
23982395
}
23992396
if (p == NULL) {
24002397
res = -1;
@@ -2408,9 +2405,7 @@ PyImport_ExtendInittab(struct _inittab *newtab)
24082405
}
24092406
memcpy(p + i, newtab, (n + 1) * sizeof(struct _inittab));
24102407
PyImport_Inittab = inittab_copy = p;
2411-
24122408
done:
2413-
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
24142409
return res;
24152410
}
24162411

@@ -2445,7 +2440,7 @@ init_builtin_modules_table(void)
24452440
size++;
24462441

24472442
/* Make the copy. */
2448-
struct _inittab *copied = PyMem_RawMalloc(size * sizeof(struct _inittab));
2443+
struct _inittab *copied = _PyMem_DefaultRawMalloc(size * sizeof(struct _inittab));
24492444
if (copied == NULL) {
24502445
return -1;
24512446
}
@@ -2459,7 +2454,7 @@ fini_builtin_modules_table(void)
24592454
{
24602455
struct _inittab *inittab = INITTAB;
24612456
INITTAB = NULL;
2462-
PyMem_RawFree(inittab);
2457+
_PyMem_DefaultRawFree(inittab);
24632458
}
24642459

24652460
PyObject *
@@ -3977,22 +3972,10 @@ _PyImport_Init(void)
39773972
if (INITTAB != NULL) {
39783973
return _PyStatus_ERR("global import state already initialized");
39793974
}
3980-
3981-
PyStatus status = _PyStatus_OK();
3982-
3983-
/* Force default raw memory allocator to get a known allocator to be able
3984-
to release the memory in _PyImport_Fini() */
3985-
PyMemAllocatorEx old_alloc;
3986-
_PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
3987-
39883975
if (init_builtin_modules_table() != 0) {
3989-
status = PyStatus_NoMemory();
3990-
goto done;
3976+
return PyStatus_NoMemory();
39913977
}
3992-
3993-
done:
3994-
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
3995-
return status;
3978+
return _PyStatus_OK();
39963979
}
39973980

39983981
void
@@ -4003,31 +3986,19 @@ _PyImport_Fini(void)
40033986
// ever dlclose() the module files?
40043987
_extensions_cache_clear_all();
40053988

4006-
/* Use the same memory allocator as _PyImport_Init(). */
4007-
PyMemAllocatorEx old_alloc;
4008-
_PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
4009-
40103989
/* Free memory allocated by _PyImport_Init() */
40113990
fini_builtin_modules_table();
4012-
4013-
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
40143991
}
40153992

40163993
void
40173994
_PyImport_Fini2(void)
40183995
{
4019-
/* Use the same memory allocator than PyImport_ExtendInittab(). */
4020-
PyMemAllocatorEx old_alloc;
4021-
_PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
4022-
40233996
// Reset PyImport_Inittab
40243997
PyImport_Inittab = _PyImport_Inittab;
40253998

40263999
/* Free memory allocated by PyImport_ExtendInittab() */
4027-
PyMem_RawFree(inittab_copy);
4000+
_PyMem_DefaultRawFree(inittab_copy);
40284001
inittab_copy = NULL;
4029-
4030-
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
40314002
}
40324003

40334004

Python/initconfig.c

+48-26
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
#include "pycore_pathconfig.h" // _Py_path_config
88
#include "pycore_pyerrors.h" // _PyErr_GetRaisedException()
99
#include "pycore_pylifecycle.h" // _Py_PreInitializeFromConfig()
10-
#include "pycore_pymem.h" // _PyMem_SetDefaultAllocator()
10+
#include "pycore_pymem.h" // _PyMem_DefaultRawMalloc()
1111
#include "pycore_pystate.h" // _PyThreadState_GET()
1212
#include "pycore_pystats.h" // _Py_StatsOn()
1313
#include "pycore_sysmodule.h" // _PySys_SetIntMaxStrDigits()
@@ -619,53 +619,87 @@ _PyWideStringList_CheckConsistency(const PyWideStringList *list)
619619
#endif /* Py_DEBUG */
620620

621621

622-
void
623-
_PyWideStringList_Clear(PyWideStringList *list)
622+
static void
623+
_PyWideStringList_ClearEx(PyWideStringList *list,
624+
bool use_default_allocator)
624625
{
625626
assert(_PyWideStringList_CheckConsistency(list));
626627
for (Py_ssize_t i=0; i < list->length; i++) {
627-
PyMem_RawFree(list->items[i]);
628+
if (use_default_allocator) {
629+
_PyMem_DefaultRawFree(list->items[i]);
630+
}
631+
else {
632+
PyMem_RawFree(list->items[i]);
633+
}
634+
}
635+
if (use_default_allocator) {
636+
_PyMem_DefaultRawFree(list->items);
637+
}
638+
else {
639+
PyMem_RawFree(list->items);
628640
}
629-
PyMem_RawFree(list->items);
630641
list->length = 0;
631642
list->items = NULL;
632643
}
633644

645+
void
646+
_PyWideStringList_Clear(PyWideStringList *list)
647+
{
648+
_PyWideStringList_ClearEx(list, false);
649+
}
634650

635-
int
636-
_PyWideStringList_Copy(PyWideStringList *list, const PyWideStringList *list2)
651+
static int
652+
_PyWideStringList_CopyEx(PyWideStringList *list,
653+
const PyWideStringList *list2,
654+
bool use_default_allocator)
637655
{
638656
assert(_PyWideStringList_CheckConsistency(list));
639657
assert(_PyWideStringList_CheckConsistency(list2));
640658

641659
if (list2->length == 0) {
642-
_PyWideStringList_Clear(list);
660+
_PyWideStringList_ClearEx(list, use_default_allocator);
643661
return 0;
644662
}
645663

646664
PyWideStringList copy = _PyWideStringList_INIT;
647665

648666
size_t size = list2->length * sizeof(list2->items[0]);
649-
copy.items = PyMem_RawMalloc(size);
667+
if (use_default_allocator) {
668+
copy.items = _PyMem_DefaultRawMalloc(size);
669+
}
670+
else {
671+
copy.items = PyMem_RawMalloc(size);
672+
}
650673
if (copy.items == NULL) {
651674
return -1;
652675
}
653676

654677
for (Py_ssize_t i=0; i < list2->length; i++) {
655-
wchar_t *item = _PyMem_RawWcsdup(list2->items[i]);
678+
wchar_t *item;
679+
if (use_default_allocator) {
680+
item = _PyMem_DefaultRawWcsdup(list2->items[i]);
681+
}
682+
else {
683+
item = _PyMem_RawWcsdup(list2->items[i]);
684+
}
656685
if (item == NULL) {
657-
_PyWideStringList_Clear(&copy);
686+
_PyWideStringList_ClearEx(&copy, use_default_allocator);
658687
return -1;
659688
}
660689
copy.items[i] = item;
661690
copy.length = i + 1;
662691
}
663692

664-
_PyWideStringList_Clear(list);
693+
_PyWideStringList_ClearEx(list, use_default_allocator);
665694
*list = copy;
666695
return 0;
667696
}
668697

698+
int
699+
_PyWideStringList_Copy(PyWideStringList *list, const PyWideStringList *list2)
700+
{
701+
return _PyWideStringList_CopyEx(list, list2, false);
702+
}
669703

670704
PyStatus
671705
PyWideStringList_Insert(PyWideStringList *list,
@@ -789,30 +823,18 @@ _PyWideStringList_AsTuple(const PyWideStringList *list)
789823
void
790824
_Py_ClearArgcArgv(void)
791825
{
792-
PyMemAllocatorEx old_alloc;
793-
_PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
794-
795-
_PyWideStringList_Clear(&_PyRuntime.orig_argv);
796-
797-
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
826+
_PyWideStringList_ClearEx(&_PyRuntime.orig_argv, true);
798827
}
799828

800829

801830
static int
802831
_Py_SetArgcArgv(Py_ssize_t argc, wchar_t * const *argv)
803832
{
804833
const PyWideStringList argv_list = {.length = argc, .items = (wchar_t **)argv};
805-
int res;
806-
807-
PyMemAllocatorEx old_alloc;
808-
_PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
809834

810835
// XXX _PyRuntime.orig_argv only gets cleared by Py_Main(),
811836
// so it currently leaks for embedders.
812-
res = _PyWideStringList_Copy(&_PyRuntime.orig_argv, &argv_list);
813-
814-
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
815-
return res;
837+
return _PyWideStringList_CopyEx(&_PyRuntime.orig_argv, &argv_list, true);
816838
}
817839

818840

0 commit comments

Comments
 (0)