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]

Reply via email to