https://github.com/python/cpython/commit/356a9e646c0e00a8f8941cab1a9bd9d58597fd15
commit: 356a9e646c0e00a8f8941cab1a9bd9d58597fd15
branch: 3.13
author: Sam Gross <[email protected]>
committer: colesbury <[email protected]>
date: 2025-02-06T13:27:30-05:00
summary:
[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 51b4edb1a4092f60d84f7d14eb41c12085e39c31)
files:
A Misc/NEWS.d/next/Core and
Builtins/2025-02-04-21-26-05.gh-issue-129668.zDanyM.rst
M Include/internal/pycore_exceptions.h
M Objects/exceptions.c
diff --git a/Include/internal/pycore_exceptions.h
b/Include/internal/pycore_exceptions.h
index 4a9df709131998..26456d1966bbb0 100644
--- a/Include/internal/pycore_exceptions.h
+++ b/Include/internal/pycore_exceptions.h
@@ -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;
};
diff --git a/Misc/NEWS.d/next/Core and
Builtins/2025-02-04-21-26-05.gh-issue-129668.zDanyM.rst b/Misc/NEWS.d/next/Core
and Builtins/2025-02-04-21-26-05.gh-issue-129668.zDanyM.rst
new file mode 100644
index 00000000000000..e42ef57c3164a1
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and
Builtins/2025-02-04-21-26-05.gh-issue-129668.zDanyM.rst
@@ -0,0 +1,2 @@
+Fix race condition when raising :exc:`MemoryError` in the free threaded
+build.
diff --git a/Objects/exceptions.c b/Objects/exceptions.c
index b5f3a60baff9a7..a1fda3c1fd16b5 100644
--- a/Objects/exceptions.c
+++ b/Objects/exceptions.c
@@ -3313,36 +3313,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;
- }
- /* 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 */
+ 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->args == NULL) {
- return NULL;
+ if (self != NULL) {
+ return (PyObject *)self;
}
- state->memerrors_freelist = (PyBaseExceptionObject *) self->dict;
- state->memerrors_numfree--;
- self->dict = NULL;
- _Py_NewReference((PyObject *)self);
- _PyObject_GC_TRACK(self);
- return (PyObject *)self;
+ if (!allow_allocation) {
+ PyInterpreterState *interp = _PyInterpreterState_GET();
+ return Py_NewRef(
+ &_Py_INTERP_SINGLETON(interp, last_resort_memory_error));
+ }
+ return BaseException_new((PyTypeObject *)PyExc_MemoryError, args, kwds);
}
static PyObject *
@@ -3387,14 +3394,17 @@ MemoryError_dealloc(PyBaseExceptionObject *self)
}
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
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]