]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-129668: Fix thread-safety of MemoryError freelist in free threaded build (gh-129704)
authorSam Gross <colesbury@gmail.com>
Thu, 6 Feb 2025 17:38:12 +0000 (12:38 -0500)
committerGitHub <noreply@github.com>
Thu, 6 Feb 2025 17:38:12 +0000 (12:38 -0500)
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.

Include/internal/pycore_exceptions.h
Misc/NEWS.d/next/Core_and_Builtins/2025-02-04-21-26-05.gh-issue-129668.zDanyM.rst [new file with mode: 0644]
Objects/exceptions.c

index 4a9df709131998a1a74cb433626c391426a43efd..26456d1966bbb0eb6a7becedabe8054d49d8dac7 100644 (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;
 };
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 (file)
index 0000000..e42ef57
--- /dev/null
@@ -0,0 +1,2 @@
+Fix race condition when raising :exc:`MemoryError` in the free threaded
+build.
index ea2733435fc3ecfb74a43dbd3c52797c0b8f455a..154cde9316866efe9519c29036920da1eb863892 100644 (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;
-    }
 
-    /* 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 *
@@ -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