gh-129668: Fix thread-safety of MemoryError freelist in free threaded build (gh-129704)

The MemoryError freelist was not thread-safe in the free threaded build.
Use a mutex to protect accesses to the freelist. Unlike other freelists,
the MemoryError freelist is not performance sensitive.
This commit is contained in:
Sam Gross 2025-02-06 12:38:12 -05:00 committed by GitHub
parent 4d56c40440
commit 51b4edb1a4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 42 additions and 27 deletions

View file

@ -24,6 +24,9 @@ struct _Py_exc_state {
PyObject *errnomap; PyObject *errnomap;
PyBaseExceptionObject *memerrors_freelist; PyBaseExceptionObject *memerrors_freelist;
int memerrors_numfree; int memerrors_numfree;
#ifdef Py_GIL_DISABLED
PyMutex memerrors_lock;
#endif
// The ExceptionGroup type // The ExceptionGroup type
PyObject *PyExc_ExceptionGroup; PyObject *PyExc_ExceptionGroup;
}; };

View file

@ -0,0 +1,2 @@
Fix race condition when raising :exc:`MemoryError` in the free threaded
build.

View file

@ -3832,36 +3832,43 @@ SimpleExtendsException(PyExc_Exception, ReferenceError,
#define MEMERRORS_SAVE 16 #define MEMERRORS_SAVE 16
#ifdef Py_GIL_DISABLED
# define MEMERRORS_LOCK(state) PyMutex_LockFlags(&state->memerrors_lock, _Py_LOCK_DONT_DETACH)
# define MEMERRORS_UNLOCK(state) PyMutex_Unlock(&state->memerrors_lock)
#else
# define MEMERRORS_LOCK(state) ((void)0)
# define MEMERRORS_UNLOCK(state) ((void)0)
#endif
static PyObject * static PyObject *
get_memory_error(int allow_allocation, PyObject *args, PyObject *kwds) get_memory_error(int allow_allocation, PyObject *args, PyObject *kwds)
{ {
PyBaseExceptionObject *self; PyBaseExceptionObject *self = NULL;
struct _Py_exc_state *state = get_exc_state(); struct _Py_exc_state *state = get_exc_state();
if (state->memerrors_freelist == NULL) {
if (!allow_allocation) { MEMERRORS_LOCK(state);
PyInterpreterState *interp = _PyInterpreterState_GET(); if (state->memerrors_freelist != NULL) {
return Py_NewRef( /* Fetch MemoryError from freelist and initialize it */
&_Py_INTERP_SINGLETON(interp, last_resort_memory_error)); self = state->memerrors_freelist;
} state->memerrors_freelist = (PyBaseExceptionObject *) self->dict;
PyObject *result = BaseException_new((PyTypeObject *)PyExc_MemoryError, args, kwds); state->memerrors_numfree--;
return result; self->dict = NULL;
self->args = (PyObject *)&_Py_SINGLETON(tuple_empty);
_Py_NewReference((PyObject *)self);
_PyObject_GC_TRACK(self);
}
MEMERRORS_UNLOCK(state);
if (self != NULL) {
return (PyObject *)self;
} }
/* Fetch object from freelist and revive it */ if (!allow_allocation) {
self = state->memerrors_freelist; PyInterpreterState *interp = _PyInterpreterState_GET();
self->args = PyTuple_New(0); return Py_NewRef(
/* This shouldn't happen since the empty tuple is persistent */ &_Py_INTERP_SINGLETON(interp, last_resort_memory_error));
if (self->args == NULL) {
return NULL;
} }
return BaseException_new((PyTypeObject *)PyExc_MemoryError, args, kwds);
state->memerrors_freelist = (PyBaseExceptionObject *) self->dict;
state->memerrors_numfree--;
self->dict = NULL;
_Py_NewReference((PyObject *)self);
_PyObject_GC_TRACK(self);
return (PyObject *)self;
} }
static PyObject * static PyObject *
@ -3907,14 +3914,17 @@ MemoryError_dealloc(PyObject *obj)
} }
struct _Py_exc_state *state = get_exc_state(); struct _Py_exc_state *state = get_exc_state();
if (state->memerrors_numfree >= MEMERRORS_SAVE) { MEMERRORS_LOCK(state);
Py_TYPE(self)->tp_free((PyObject *)self); if (state->memerrors_numfree < MEMERRORS_SAVE) {
}
else {
self->dict = (PyObject *) state->memerrors_freelist; self->dict = (PyObject *) state->memerrors_freelist;
state->memerrors_freelist = self; state->memerrors_freelist = self;
state->memerrors_numfree++; state->memerrors_numfree++;
MEMERRORS_UNLOCK(state);
return;
} }
MEMERRORS_UNLOCK(state);
Py_TYPE(self)->tp_free((PyObject *)self);
} }
static int static int