Skip to content

Commit 51b4edb

Browse files
authored
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.
1 parent 4d56c40 commit 51b4edb

File tree

3 files changed

+41
-26
lines changed

3 files changed

+41
-26
lines changed

Include/internal/pycore_exceptions.h

+3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ struct _Py_exc_state {
2424
PyObject *errnomap;
2525
PyBaseExceptionObject *memerrors_freelist;
2626
int memerrors_numfree;
27+
#ifdef Py_GIL_DISABLED
28+
PyMutex memerrors_lock;
29+
#endif
2730
// The ExceptionGroup type
2831
PyObject *PyExc_ExceptionGroup;
2932
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix race condition when raising :exc:`MemoryError` in the free threaded
2+
build.

Objects/exceptions.c

+36-26
Original file line numberDiff line numberDiff line change
@@ -3832,36 +3832,43 @@ SimpleExtendsException(PyExc_Exception, ReferenceError,
38323832

38333833
#define MEMERRORS_SAVE 16
38343834

3835+
#ifdef Py_GIL_DISABLED
3836+
# define MEMERRORS_LOCK(state) PyMutex_LockFlags(&state->memerrors_lock, _Py_LOCK_DONT_DETACH)
3837+
# define MEMERRORS_UNLOCK(state) PyMutex_Unlock(&state->memerrors_lock)
3838+
#else
3839+
# define MEMERRORS_LOCK(state) ((void)0)
3840+
# define MEMERRORS_UNLOCK(state) ((void)0)
3841+
#endif
3842+
38353843
static PyObject *
38363844
get_memory_error(int allow_allocation, PyObject *args, PyObject *kwds)
38373845
{
3838-
PyBaseExceptionObject *self;
3846+
PyBaseExceptionObject *self = NULL;
38393847
struct _Py_exc_state *state = get_exc_state();
3840-
if (state->memerrors_freelist == NULL) {
3841-
if (!allow_allocation) {
3842-
PyInterpreterState *interp = _PyInterpreterState_GET();
3843-
return Py_NewRef(
3844-
&_Py_INTERP_SINGLETON(interp, last_resort_memory_error));
3845-
}
3846-
PyObject *result = BaseException_new((PyTypeObject *)PyExc_MemoryError, args, kwds);
3847-
return result;
3848-
}
38493848

3850-
/* Fetch object from freelist and revive it */
3851-
self = state->memerrors_freelist;
3852-
self->args = PyTuple_New(0);
3853-
/* This shouldn't happen since the empty tuple is persistent */
3849+
MEMERRORS_LOCK(state);
3850+
if (state->memerrors_freelist != NULL) {
3851+
/* Fetch MemoryError from freelist and initialize it */
3852+
self = state->memerrors_freelist;
3853+
state->memerrors_freelist = (PyBaseExceptionObject *) self->dict;
3854+
state->memerrors_numfree--;
3855+
self->dict = NULL;
3856+
self->args = (PyObject *)&_Py_SINGLETON(tuple_empty);
3857+
_Py_NewReference((PyObject *)self);
3858+
_PyObject_GC_TRACK(self);
3859+
}
3860+
MEMERRORS_UNLOCK(state);
38543861

3855-
if (self->args == NULL) {
3856-
return NULL;
3862+
if (self != NULL) {
3863+
return (PyObject *)self;
38573864
}
38583865

3859-
state->memerrors_freelist = (PyBaseExceptionObject *) self->dict;
3860-
state->memerrors_numfree--;
3861-
self->dict = NULL;
3862-
_Py_NewReference((PyObject *)self);
3863-
_PyObject_GC_TRACK(self);
3864-
return (PyObject *)self;
3866+
if (!allow_allocation) {
3867+
PyInterpreterState *interp = _PyInterpreterState_GET();
3868+
return Py_NewRef(
3869+
&_Py_INTERP_SINGLETON(interp, last_resort_memory_error));
3870+
}
3871+
return BaseException_new((PyTypeObject *)PyExc_MemoryError, args, kwds);
38653872
}
38663873

38673874
static PyObject *
@@ -3907,14 +3914,17 @@ MemoryError_dealloc(PyObject *obj)
39073914
}
39083915

39093916
struct _Py_exc_state *state = get_exc_state();
3910-
if (state->memerrors_numfree >= MEMERRORS_SAVE) {
3911-
Py_TYPE(self)->tp_free((PyObject *)self);
3912-
}
3913-
else {
3917+
MEMERRORS_LOCK(state);
3918+
if (state->memerrors_numfree < MEMERRORS_SAVE) {
39143919
self->dict = (PyObject *) state->memerrors_freelist;
39153920
state->memerrors_freelist = self;
39163921
state->memerrors_numfree++;
3922+
MEMERRORS_UNLOCK(state);
3923+
return;
39173924
}
3925+
MEMERRORS_UNLOCK(state);
3926+
3927+
Py_TYPE(self)->tp_free((PyObject *)self);
39183928
}
39193929

39203930
static int

0 commit comments

Comments
 (0)