]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-124218: Avoid refcount contention on builtins module (GH-125847)
authorSam Gross <colesbury@gmail.com>
Thu, 24 Oct 2024 16:44:38 +0000 (12:44 -0400)
committerGitHub <noreply@github.com>
Thu, 24 Oct 2024 16:44:38 +0000 (12:44 -0400)
This replaces `_PyEval_BuiltinsFromGlobals` with
`_PyDict_LoadBuiltinsFromGlobals`, which returns a new reference
instead of a borrowed reference. Internally, the new function uses
per-thread reference counting when possible to avoid contention on the
refcount fields on the builtins module.

Include/internal/pycore_ceval.h
Include/internal/pycore_dict.h
Objects/dictobject.c
Objects/frameobject.c
Objects/funcobject.c
Python/ceval.c

index cff2b1f711479335584aec2b195df6c40fa5f646..411bbff106dd6988abc9c6165063f1a1b2d8c246 100644 (file)
@@ -83,9 +83,6 @@ extern void _PyEval_Fini(void);
 
 
 extern PyObject* _PyEval_GetBuiltins(PyThreadState *tstate);
-extern PyObject* _PyEval_BuiltinsFromGlobals(
-    PyThreadState *tstate,
-    PyObject *globals);
 
 // Trampoline API
 
index 1d185559b3ef437a098b91d6ca473ae2a6688c96..c5399ad8e0497f511f6324d0165fcd27bbae7160 100644 (file)
@@ -108,6 +108,9 @@ extern Py_ssize_t _PyDictKeys_StringLookup(PyDictKeysObject* dictkeys, PyObject
 PyAPI_FUNC(PyObject *)_PyDict_LoadGlobal(PyDictObject *, PyDictObject *, PyObject *);
 PyAPI_FUNC(void) _PyDict_LoadGlobalStackRef(PyDictObject *, PyDictObject *, PyObject *, _PyStackRef *);
 
+// Loads the __builtins__ object from the globals dict. Returns a new reference.
+extern PyObject *_PyDict_LoadBuiltinsFromGlobals(PyObject *globals);
+
 /* Consumes references to key and value */
 PyAPI_FUNC(int) _PyDict_SetItem_Take2(PyDictObject *op, PyObject *key, PyObject *value);
 extern int _PyDict_SetItem_LockHeld(PyDictObject *dict, PyObject *name, PyObject *value);
@@ -318,6 +321,8 @@ PyDictObject *_PyObject_MaterializeManagedDict_LockHeld(PyObject *);
 #ifndef Py_GIL_DISABLED
 #  define _Py_INCREF_DICT Py_INCREF
 #  define _Py_DECREF_DICT Py_DECREF
+#  define _Py_INCREF_BUILTINS Py_INCREF
+#  define _Py_DECREF_BUILTINS Py_DECREF
 #else
 static inline Py_ssize_t
 _PyDict_UniqueId(PyDictObject *mp)
@@ -341,6 +346,30 @@ _Py_DECREF_DICT(PyObject *op)
     Py_ssize_t id = _PyDict_UniqueId((PyDictObject *)op);
     _Py_THREAD_DECREF_OBJECT(op, id);
 }
+
+// Like `_Py_INCREF_DICT`, but also handles non-dict objects because builtins
+// may not be a dict.
+static inline void
+_Py_INCREF_BUILTINS(PyObject *op)
+{
+    if (PyDict_CheckExact(op)) {
+        _Py_INCREF_DICT(op);
+    }
+    else {
+        Py_INCREF(op);
+    }
+}
+
+static inline void
+_Py_DECREF_BUILTINS(PyObject *op)
+{
+    if (PyDict_CheckExact(op)) {
+        _Py_DECREF_DICT(op);
+    }
+    else {
+        Py_DECREF(op);
+    }
+}
 #endif
 
 #ifdef __cplusplus
index 3134f6141dc9be7dfc12a736a20f0b9edd4f90b6..68ba2f74fdc67a2fd90c8d4bd987f7b8b05032e4 100644 (file)
@@ -2511,6 +2511,40 @@ _PyDict_LoadGlobalStackRef(PyDictObject *globals, PyDictObject *builtins, PyObje
     assert(ix >= 0 || PyStackRef_IsNull(*res));
 }
 
+PyObject *
+_PyDict_LoadBuiltinsFromGlobals(PyObject *globals)
+{
+    if (!PyDict_Check(globals)) {
+        PyErr_BadInternalCall();
+        return NULL;
+    }
+
+    PyDictObject *mp = (PyDictObject *)globals;
+    PyObject *key = &_Py_ID(__builtins__);
+    Py_hash_t hash = unicode_get_hash(key);
+
+    // Use the stackref variant to avoid reference count contention on the
+    // builtins module in the free threading build. It's important not to
+    // make any escaping calls between the lookup and the `PyStackRef_CLOSE()`
+    // because the `ref` is not visible to the GC.
+    _PyStackRef ref;
+    Py_ssize_t ix = _Py_dict_lookup_threadsafe_stackref(mp, key, hash, &ref);
+    if (ix == DKIX_ERROR) {
+        return NULL;
+    }
+    if (PyStackRef_IsNull(ref)) {
+        return Py_NewRef(PyEval_GetBuiltins());
+    }
+    PyObject *builtins = PyStackRef_AsPyObjectBorrow(ref);
+    if (PyModule_Check(builtins)) {
+        builtins = _PyModule_GetDict(builtins);
+        assert(builtins != NULL);
+    }
+    _Py_INCREF_BUILTINS(builtins);
+    PyStackRef_CLOSE(ref);
+    return builtins;
+}
+
 /* Consumes references to key and value */
 static int
 setitem_take2_lock_held(PyDictObject *mp, PyObject *key, PyObject *value)
index 5ef48919a081bea91bf43c2e0bdac2ef33c84695..af2a2ef18e627ae8762c73627158d29b15124a27 100644 (file)
@@ -1,8 +1,9 @@
 /* Frame object implementation */
 
 #include "Python.h"
-#include "pycore_ceval.h"         // _PyEval_BuiltinsFromGlobals()
+#include "pycore_ceval.h"         // _PyEval_SetOpcodeTrace()
 #include "pycore_code.h"          // CO_FAST_LOCAL, etc.
+#include "pycore_dict.h"          // _PyDict_LoadBuiltinsFromGlobals()
 #include "pycore_function.h"      // _PyFunction_FromConstructor()
 #include "pycore_moduleobject.h"  // _PyModule_GetDict()
 #include "pycore_modsupport.h"    // _PyArg_CheckPositional()
@@ -1899,7 +1900,7 @@ PyFrameObject*
 PyFrame_New(PyThreadState *tstate, PyCodeObject *code,
             PyObject *globals, PyObject *locals)
 {
-    PyObject *builtins = _PyEval_BuiltinsFromGlobals(tstate, globals); // borrowed ref
+    PyObject *builtins = _PyDict_LoadBuiltinsFromGlobals(globals);
     if (builtins == NULL) {
         return NULL;
     }
@@ -1914,6 +1915,7 @@ PyFrame_New(PyThreadState *tstate, PyCodeObject *code,
         .fc_closure = NULL
     };
     PyFunctionObject *func = _PyFunction_FromConstructor(&desc);
+    _Py_DECREF_BUILTINS(builtins);
     if (func == NULL) {
         return NULL;
     }
@@ -2204,21 +2206,3 @@ PyFrame_GetGenerator(PyFrameObject *frame)
     PyGenObject *gen = _PyGen_GetGeneratorFromFrame(frame->f_frame);
     return Py_NewRef(gen);
 }
-
-PyObject*
-_PyEval_BuiltinsFromGlobals(PyThreadState *tstate, PyObject *globals)
-{
-    PyObject *builtins = PyDict_GetItemWithError(globals, &_Py_ID(__builtins__));
-    if (builtins) {
-        if (PyModule_Check(builtins)) {
-            builtins = _PyModule_GetDict(builtins);
-            assert(builtins != NULL);
-        }
-        return builtins;
-    }
-    if (PyErr_Occurred()) {
-        return NULL;
-    }
-
-    return _PyEval_GetBuiltins(tstate);
-}
index 44fb4ac0907d7b827505215891a9f5aa09a94bb9..e72a7d98c0a79e444f1c35705a44eed9de14b6eb 100644 (file)
@@ -2,7 +2,6 @@
 /* Function object implementation */
 
 #include "Python.h"
-#include "pycore_ceval.h"         // _PyEval_BuiltinsFromGlobals()
 #include "pycore_dict.h"          // _Py_INCREF_DICT()
 #include "pycore_long.h"          // _PyLong_GetOne()
 #include "pycore_modsupport.h"    // _PyArg_NoKeywords()
@@ -115,12 +114,7 @@ _PyFunction_FromConstructor(PyFrameConstructor *constr)
     }
     _Py_INCREF_DICT(constr->fc_globals);
     op->func_globals = constr->fc_globals;
-    if (PyDict_Check(constr->fc_builtins)) {
-        _Py_INCREF_DICT(constr->fc_builtins);
-    }
-    else {
-        Py_INCREF(constr->fc_builtins);
-    }
+    _Py_INCREF_BUILTINS(constr->fc_builtins);
     op->func_builtins = constr->fc_builtins;
     op->func_name = Py_NewRef(constr->fc_name);
     op->func_qualname = Py_NewRef(constr->fc_qualname);
@@ -153,8 +147,6 @@ PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname
     assert(PyDict_Check(globals));
     _Py_INCREF_DICT(globals);
 
-    PyThreadState *tstate = _PyThreadState_GET();
-
     PyCodeObject *code_obj = (PyCodeObject *)code;
     _Py_INCREF_CODE(code_obj);
 
@@ -188,16 +180,10 @@ PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname
         goto error;
     }
 
-    builtins = _PyEval_BuiltinsFromGlobals(tstate, globals); // borrowed ref
+    builtins = _PyDict_LoadBuiltinsFromGlobals(globals);
     if (builtins == NULL) {
         goto error;
     }
-    if (PyDict_Check(builtins)) {
-        _Py_INCREF_DICT(builtins);
-    }
-    else {
-        Py_INCREF(builtins);
-    }
 
     PyFunctionObject *op = PyObject_GC_New(PyFunctionObject, &PyFunction_Type);
     if (op == NULL) {
@@ -1078,12 +1064,7 @@ func_clear(PyObject *self)
     PyObject *builtins = op->func_builtins;
     op->func_builtins = NULL;
     if (builtins != NULL) {
-        if (PyDict_Check(builtins)) {
-            _Py_DECREF_DICT(builtins);
-        }
-        else {
-            Py_DECREF(builtins);
-        }
+        _Py_DECREF_BUILTINS(builtins);
     }
     Py_CLEAR(op->func_module);
     Py_CLEAR(op->func_defaults);
index ca75646b585f079505fbcb62578c8a64865a83ba..ece7ef1d32048fe5d3ff7d43da5127871f30ada4 100644 (file)
@@ -639,7 +639,7 @@ PyEval_EvalCode(PyObject *co, PyObject *globals, PyObject *locals)
     if (locals == NULL) {
         locals = globals;
     }
-    PyObject *builtins = _PyEval_BuiltinsFromGlobals(tstate, globals); // borrowed ref
+    PyObject *builtins = _PyDict_LoadBuiltinsFromGlobals(globals);
     if (builtins == NULL) {
         return NULL;
     }
@@ -654,6 +654,7 @@ PyEval_EvalCode(PyObject *co, PyObject *globals, PyObject *locals)
         .fc_closure = NULL
     };
     PyFunctionObject *func = _PyFunction_FromConstructor(&desc);
+    _Py_DECREF_BUILTINS(builtins);
     if (func == NULL) {
         return NULL;
     }
@@ -1899,7 +1900,7 @@ PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals,
     if (defaults == NULL) {
         return NULL;
     }
-    PyObject *builtins = _PyEval_BuiltinsFromGlobals(tstate, globals); // borrowed ref
+    PyObject *builtins = _PyDict_LoadBuiltinsFromGlobals(globals);
     if (builtins == NULL) {
         Py_DECREF(defaults);
         return NULL;
@@ -1954,6 +1955,7 @@ fail:
     Py_XDECREF(func);
     Py_XDECREF(kwnames);
     PyMem_Free(newargs);
+    _Py_DECREF_BUILTINS(builtins);
     Py_DECREF(defaults);
     return res;
 }