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;
PyBaseExceptionObject *memerrors_freelist;
int memerrors_numfree;
#ifdef Py_GIL_DISABLED
PyMutex memerrors_lock;
#endif
// The ExceptionGroup type
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
#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 *
get_memory_error(int allow_allocation, PyObject *args, PyObject *kwds)
{
PyBaseExceptionObject *self;
PyBaseExceptionObject *self = NULL;
struct _Py_exc_state *state = get_exc_state();
if (state->memerrors_freelist == NULL) {
if (!allow_allocation) {
PyInterpreterState *interp = _PyInterpreterState_GET();
return Py_NewRef(
&_Py_INTERP_SINGLETON(interp, last_resort_memory_error));
}
PyObject *result = BaseException_new((PyTypeObject *)PyExc_MemoryError, args, kwds);
return result;
MEMERRORS_LOCK(state);
if (state->memerrors_freelist != NULL) {
/* Fetch MemoryError from freelist and initialize it */
self = state->memerrors_freelist;
state->memerrors_freelist = (PyBaseExceptionObject *) self->dict;
state->memerrors_numfree--;
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 */
self = state->memerrors_freelist;
self->args = PyTuple_New(0);
/* This shouldn't happen since the empty tuple is persistent */
if (self->args == NULL) {
return NULL;
if (!allow_allocation) {
PyInterpreterState *interp = _PyInterpreterState_GET();
return Py_NewRef(
&_Py_INTERP_SINGLETON(interp, last_resort_memory_error));
}
state->memerrors_freelist = (PyBaseExceptionObject *) self->dict;
state->memerrors_numfree--;
self->dict = NULL;
_Py_NewReference((PyObject *)self);
_PyObject_GC_TRACK(self);
return (PyObject *)self;
return BaseException_new((PyTypeObject *)PyExc_MemoryError, args, kwds);
}
static PyObject *
@ -3907,14 +3914,17 @@ MemoryError_dealloc(PyObject *obj)
}
struct _Py_exc_state *state = get_exc_state();
if (state->memerrors_numfree >= MEMERRORS_SAVE) {
Py_TYPE(self)->tp_free((PyObject *)self);
}
else {
MEMERRORS_LOCK(state);
if (state->memerrors_numfree < MEMERRORS_SAVE) {
self->dict = (PyObject *) state->memerrors_freelist;
state->memerrors_freelist = self;
state->memerrors_numfree++;
MEMERRORS_UNLOCK(state);
return;
}
MEMERRORS_UNLOCK(state);
Py_TYPE(self)->tp_free((PyObject *)self);
}
static int