Skip to content

Commit 3c4a7fa

Browse files
authored
gh-124218: Avoid refcount contention on builtins module (GH-125847)
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.
1 parent 5003ad5 commit 3c4a7fa

File tree

6 files changed

+74
-47
lines changed

6 files changed

+74
-47
lines changed

Include/internal/pycore_ceval.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,6 @@ extern void _PyEval_Fini(void);
8383

8484

8585
extern PyObject* _PyEval_GetBuiltins(PyThreadState *tstate);
86-
extern PyObject* _PyEval_BuiltinsFromGlobals(
87-
PyThreadState *tstate,
88-
PyObject *globals);
8986

9087
// Trampoline API
9188

Include/internal/pycore_dict.h

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ extern Py_ssize_t _PyDictKeys_StringLookup(PyDictKeysObject* dictkeys, PyObject
108108
PyAPI_FUNC(PyObject *)_PyDict_LoadGlobal(PyDictObject *, PyDictObject *, PyObject *);
109109
PyAPI_FUNC(void) _PyDict_LoadGlobalStackRef(PyDictObject *, PyDictObject *, PyObject *, _PyStackRef *);
110110

111+
// Loads the __builtins__ object from the globals dict. Returns a new reference.
112+
extern PyObject *_PyDict_LoadBuiltinsFromGlobals(PyObject *globals);
113+
111114
/* Consumes references to key and value */
112115
PyAPI_FUNC(int) _PyDict_SetItem_Take2(PyDictObject *op, PyObject *key, PyObject *value);
113116
extern int _PyDict_SetItem_LockHeld(PyDictObject *dict, PyObject *name, PyObject *value);
@@ -318,6 +321,8 @@ PyDictObject *_PyObject_MaterializeManagedDict_LockHeld(PyObject *);
318321
#ifndef Py_GIL_DISABLED
319322
# define _Py_INCREF_DICT Py_INCREF
320323
# define _Py_DECREF_DICT Py_DECREF
324+
# define _Py_INCREF_BUILTINS Py_INCREF
325+
# define _Py_DECREF_BUILTINS Py_DECREF
321326
#else
322327
static inline Py_ssize_t
323328
_PyDict_UniqueId(PyDictObject *mp)
@@ -341,6 +346,30 @@ _Py_DECREF_DICT(PyObject *op)
341346
Py_ssize_t id = _PyDict_UniqueId((PyDictObject *)op);
342347
_Py_THREAD_DECREF_OBJECT(op, id);
343348
}
349+
350+
// Like `_Py_INCREF_DICT`, but also handles non-dict objects because builtins
351+
// may not be a dict.
352+
static inline void
353+
_Py_INCREF_BUILTINS(PyObject *op)
354+
{
355+
if (PyDict_CheckExact(op)) {
356+
_Py_INCREF_DICT(op);
357+
}
358+
else {
359+
Py_INCREF(op);
360+
}
361+
}
362+
363+
static inline void
364+
_Py_DECREF_BUILTINS(PyObject *op)
365+
{
366+
if (PyDict_CheckExact(op)) {
367+
_Py_DECREF_DICT(op);
368+
}
369+
else {
370+
Py_DECREF(op);
371+
}
372+
}
344373
#endif
345374

346375
#ifdef __cplusplus

Objects/dictobject.c

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2511,6 +2511,40 @@ _PyDict_LoadGlobalStackRef(PyDictObject *globals, PyDictObject *builtins, PyObje
25112511
assert(ix >= 0 || PyStackRef_IsNull(*res));
25122512
}
25132513

2514+
PyObject *
2515+
_PyDict_LoadBuiltinsFromGlobals(PyObject *globals)
2516+
{
2517+
if (!PyDict_Check(globals)) {
2518+
PyErr_BadInternalCall();
2519+
return NULL;
2520+
}
2521+
2522+
PyDictObject *mp = (PyDictObject *)globals;
2523+
PyObject *key = &_Py_ID(__builtins__);
2524+
Py_hash_t hash = unicode_get_hash(key);
2525+
2526+
// Use the stackref variant to avoid reference count contention on the
2527+
// builtins module in the free threading build. It's important not to
2528+
// make any escaping calls between the lookup and the `PyStackRef_CLOSE()`
2529+
// because the `ref` is not visible to the GC.
2530+
_PyStackRef ref;
2531+
Py_ssize_t ix = _Py_dict_lookup_threadsafe_stackref(mp, key, hash, &ref);
2532+
if (ix == DKIX_ERROR) {
2533+
return NULL;
2534+
}
2535+
if (PyStackRef_IsNull(ref)) {
2536+
return Py_NewRef(PyEval_GetBuiltins());
2537+
}
2538+
PyObject *builtins = PyStackRef_AsPyObjectBorrow(ref);
2539+
if (PyModule_Check(builtins)) {
2540+
builtins = _PyModule_GetDict(builtins);
2541+
assert(builtins != NULL);
2542+
}
2543+
_Py_INCREF_BUILTINS(builtins);
2544+
PyStackRef_CLOSE(ref);
2545+
return builtins;
2546+
}
2547+
25142548
/* Consumes references to key and value */
25152549
static int
25162550
setitem_take2_lock_held(PyDictObject *mp, PyObject *key, PyObject *value)

Objects/frameobject.c

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
/* Frame object implementation */
22

33
#include "Python.h"
4-
#include "pycore_ceval.h" // _PyEval_BuiltinsFromGlobals()
4+
#include "pycore_ceval.h" // _PyEval_SetOpcodeTrace()
55
#include "pycore_code.h" // CO_FAST_LOCAL, etc.
6+
#include "pycore_dict.h" // _PyDict_LoadBuiltinsFromGlobals()
67
#include "pycore_function.h" // _PyFunction_FromConstructor()
78
#include "pycore_moduleobject.h" // _PyModule_GetDict()
89
#include "pycore_modsupport.h" // _PyArg_CheckPositional()
@@ -1899,7 +1900,7 @@ PyFrameObject*
18991900
PyFrame_New(PyThreadState *tstate, PyCodeObject *code,
19001901
PyObject *globals, PyObject *locals)
19011902
{
1902-
PyObject *builtins = _PyEval_BuiltinsFromGlobals(tstate, globals); // borrowed ref
1903+
PyObject *builtins = _PyDict_LoadBuiltinsFromGlobals(globals);
19031904
if (builtins == NULL) {
19041905
return NULL;
19051906
}
@@ -1914,6 +1915,7 @@ PyFrame_New(PyThreadState *tstate, PyCodeObject *code,
19141915
.fc_closure = NULL
19151916
};
19161917
PyFunctionObject *func = _PyFunction_FromConstructor(&desc);
1918+
_Py_DECREF_BUILTINS(builtins);
19171919
if (func == NULL) {
19181920
return NULL;
19191921
}
@@ -2204,21 +2206,3 @@ PyFrame_GetGenerator(PyFrameObject *frame)
22042206
PyGenObject *gen = _PyGen_GetGeneratorFromFrame(frame->f_frame);
22052207
return Py_NewRef(gen);
22062208
}
2207-
2208-
PyObject*
2209-
_PyEval_BuiltinsFromGlobals(PyThreadState *tstate, PyObject *globals)
2210-
{
2211-
PyObject *builtins = PyDict_GetItemWithError(globals, &_Py_ID(__builtins__));
2212-
if (builtins) {
2213-
if (PyModule_Check(builtins)) {
2214-
builtins = _PyModule_GetDict(builtins);
2215-
assert(builtins != NULL);
2216-
}
2217-
return builtins;
2218-
}
2219-
if (PyErr_Occurred()) {
2220-
return NULL;
2221-
}
2222-
2223-
return _PyEval_GetBuiltins(tstate);
2224-
}

Objects/funcobject.c

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
/* Function object implementation */
33

44
#include "Python.h"
5-
#include "pycore_ceval.h" // _PyEval_BuiltinsFromGlobals()
65
#include "pycore_dict.h" // _Py_INCREF_DICT()
76
#include "pycore_long.h" // _PyLong_GetOne()
87
#include "pycore_modsupport.h" // _PyArg_NoKeywords()
@@ -115,12 +114,7 @@ _PyFunction_FromConstructor(PyFrameConstructor *constr)
115114
}
116115
_Py_INCREF_DICT(constr->fc_globals);
117116
op->func_globals = constr->fc_globals;
118-
if (PyDict_Check(constr->fc_builtins)) {
119-
_Py_INCREF_DICT(constr->fc_builtins);
120-
}
121-
else {
122-
Py_INCREF(constr->fc_builtins);
123-
}
117+
_Py_INCREF_BUILTINS(constr->fc_builtins);
124118
op->func_builtins = constr->fc_builtins;
125119
op->func_name = Py_NewRef(constr->fc_name);
126120
op->func_qualname = Py_NewRef(constr->fc_qualname);
@@ -153,8 +147,6 @@ PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname
153147
assert(PyDict_Check(globals));
154148
_Py_INCREF_DICT(globals);
155149

156-
PyThreadState *tstate = _PyThreadState_GET();
157-
158150
PyCodeObject *code_obj = (PyCodeObject *)code;
159151
_Py_INCREF_CODE(code_obj);
160152

@@ -188,16 +180,10 @@ PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname
188180
goto error;
189181
}
190182

191-
builtins = _PyEval_BuiltinsFromGlobals(tstate, globals); // borrowed ref
183+
builtins = _PyDict_LoadBuiltinsFromGlobals(globals);
192184
if (builtins == NULL) {
193185
goto error;
194186
}
195-
if (PyDict_Check(builtins)) {
196-
_Py_INCREF_DICT(builtins);
197-
}
198-
else {
199-
Py_INCREF(builtins);
200-
}
201187

202188
PyFunctionObject *op = PyObject_GC_New(PyFunctionObject, &PyFunction_Type);
203189
if (op == NULL) {
@@ -1078,12 +1064,7 @@ func_clear(PyObject *self)
10781064
PyObject *builtins = op->func_builtins;
10791065
op->func_builtins = NULL;
10801066
if (builtins != NULL) {
1081-
if (PyDict_Check(builtins)) {
1082-
_Py_DECREF_DICT(builtins);
1083-
}
1084-
else {
1085-
Py_DECREF(builtins);
1086-
}
1067+
_Py_DECREF_BUILTINS(builtins);
10871068
}
10881069
Py_CLEAR(op->func_module);
10891070
Py_CLEAR(op->func_defaults);

Python/ceval.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -639,7 +639,7 @@ PyEval_EvalCode(PyObject *co, PyObject *globals, PyObject *locals)
639639
if (locals == NULL) {
640640
locals = globals;
641641
}
642-
PyObject *builtins = _PyEval_BuiltinsFromGlobals(tstate, globals); // borrowed ref
642+
PyObject *builtins = _PyDict_LoadBuiltinsFromGlobals(globals);
643643
if (builtins == NULL) {
644644
return NULL;
645645
}
@@ -654,6 +654,7 @@ PyEval_EvalCode(PyObject *co, PyObject *globals, PyObject *locals)
654654
.fc_closure = NULL
655655
};
656656
PyFunctionObject *func = _PyFunction_FromConstructor(&desc);
657+
_Py_DECREF_BUILTINS(builtins);
657658
if (func == NULL) {
658659
return NULL;
659660
}
@@ -1899,7 +1900,7 @@ PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals,
18991900
if (defaults == NULL) {
19001901
return NULL;
19011902
}
1902-
PyObject *builtins = _PyEval_BuiltinsFromGlobals(tstate, globals); // borrowed ref
1903+
PyObject *builtins = _PyDict_LoadBuiltinsFromGlobals(globals);
19031904
if (builtins == NULL) {
19041905
Py_DECREF(defaults);
19051906
return NULL;
@@ -1954,6 +1955,7 @@ PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals,
19541955
Py_XDECREF(func);
19551956
Py_XDECREF(kwnames);
19561957
PyMem_Free(newargs);
1958+
_Py_DECREF_BUILTINS(builtins);
19571959
Py_DECREF(defaults);
19581960
return res;
19591961
}

0 commit comments

Comments
 (0)