Skip to content

Commit 356a9e6

Browse files
authored
[3.13] gh-129668: Fix thread-safety of MemoryError freelist in free threaded build (gh-129704) (gh-129742)
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. (cherry picked from commit 51b4edb)
1 parent de84531 commit 356a9e6

File tree

3 files changed

+41
-26
lines changed

3 files changed

+41
-26
lines changed

Include/internal/pycore_exceptions.h

Lines changed: 3 additions & 0 deletions
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
};
Lines changed: 2 additions & 0 deletions
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

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3313,36 +3313,43 @@ SimpleExtendsException(PyExc_Exception, ReferenceError,
33133313

33143314
#define MEMERRORS_SAVE 16
33153315

3316+
#ifdef Py_GIL_DISABLED
3317+
# define MEMERRORS_LOCK(state) PyMutex_LockFlags(&state->memerrors_lock, _Py_LOCK_DONT_DETACH)
3318+
# define MEMERRORS_UNLOCK(state) PyMutex_Unlock(&state->memerrors_lock)
3319+
#else
3320+
# define MEMERRORS_LOCK(state) ((void)0)
3321+
# define MEMERRORS_UNLOCK(state) ((void)0)
3322+
#endif
3323+
33163324
static PyObject *
33173325
get_memory_error(int allow_allocation, PyObject *args, PyObject *kwds)
33183326
{
3319-
PyBaseExceptionObject *self;
3327+
PyBaseExceptionObject *self = NULL;
33203328
struct _Py_exc_state *state = get_exc_state();
3321-
if (state->memerrors_freelist == NULL) {
3322-
if (!allow_allocation) {
3323-
PyInterpreterState *interp = _PyInterpreterState_GET();
3324-
return Py_NewRef(
3325-
&_Py_INTERP_SINGLETON(interp, last_resort_memory_error));
3326-
}
3327-
PyObject *result = BaseException_new((PyTypeObject *)PyExc_MemoryError, args, kwds);
3328-
return result;
3329-
}
33303329

3331-
/* Fetch object from freelist and revive it */
3332-
self = state->memerrors_freelist;
3333-
self->args = PyTuple_New(0);
3334-
/* This shouldn't happen since the empty tuple is persistent */
3330+
MEMERRORS_LOCK(state);
3331+
if (state->memerrors_freelist != NULL) {
3332+
/* Fetch MemoryError from freelist and initialize it */
3333+
self = state->memerrors_freelist;
3334+
state->memerrors_freelist = (PyBaseExceptionObject *) self->dict;
3335+
state->memerrors_numfree--;
3336+
self->dict = NULL;
3337+
self->args = (PyObject *)&_Py_SINGLETON(tuple_empty);
3338+
_Py_NewReference((PyObject *)self);
3339+
_PyObject_GC_TRACK(self);
3340+
}
3341+
MEMERRORS_UNLOCK(state);
33353342

3336-
if (self->args == NULL) {
3337-
return NULL;
3343+
if (self != NULL) {
3344+
return (PyObject *)self;
33383345
}
33393346

3340-
state->memerrors_freelist = (PyBaseExceptionObject *) self->dict;
3341-
state->memerrors_numfree--;
3342-
self->dict = NULL;
3343-
_Py_NewReference((PyObject *)self);
3344-
_PyObject_GC_TRACK(self);
3345-
return (PyObject *)self;
3347+
if (!allow_allocation) {
3348+
PyInterpreterState *interp = _PyInterpreterState_GET();
3349+
return Py_NewRef(
3350+
&_Py_INTERP_SINGLETON(interp, last_resort_memory_error));
3351+
}
3352+
return BaseException_new((PyTypeObject *)PyExc_MemoryError, args, kwds);
33463353
}
33473354

33483355
static PyObject *
@@ -3387,14 +3394,17 @@ MemoryError_dealloc(PyBaseExceptionObject *self)
33873394
}
33883395

33893396
struct _Py_exc_state *state = get_exc_state();
3390-
if (state->memerrors_numfree >= MEMERRORS_SAVE) {
3391-
Py_TYPE(self)->tp_free((PyObject *)self);
3392-
}
3393-
else {
3397+
MEMERRORS_LOCK(state);
3398+
if (state->memerrors_numfree < MEMERRORS_SAVE) {
33943399
self->dict = (PyObject *) state->memerrors_freelist;
33953400
state->memerrors_freelist = self;
33963401
state->memerrors_numfree++;
3402+
MEMERRORS_UNLOCK(state);
3403+
return;
33973404
}
3405+
MEMERRORS_UNLOCK(state);
3406+
3407+
Py_TYPE(self)->tp_free((PyObject *)self);
33983408
}
33993409

34003410
static int

0 commit comments

Comments
 (0)