Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Include/internal/pycore_opcode_metadata.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Include/internal/pycore_uop_metadata.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 8 additions & 7 deletions Modules/_testinternalcapi/test_cases.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 17 additions & 4 deletions Python/bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -3225,14 +3225,27 @@ dummy_func(
op(_STORE_ATTR_SLOT, (index/1, value, owner -- o)) {
PyObject *owner_o = PyStackRef_AsPyObjectBorrow(owner);

DEOPT_IF(!LOCK_OBJECT(owner_o));
char *addr = (char *)owner_o + index;
STAT_INC(STORE_ATTR, hit);
// Lock-free read-modify-write on the slot pointer (free-threaded
// build). Atomic exchange returns the previous value and stores
// the new one in a single atomic op. Each concurrent writer
// observes a unique old value (so no double-decref), and
// concurrent LOAD_ATTR_SLOT readers already cope with mid-flight
// mutations via _Py_TryIncrefCompare. The C-API path
// (PyMember_SetOne for Py_T_OBJECT_EX / _Py_T_OBJECT) is updated
// to the same atomic-exchange pattern so the two paths stay
// consistent without a per-object lock. On the GIL build this
// compiles to a plain read+write.
#ifdef Py_GIL_DISABLED
PyObject *old_value = _Py_atomic_exchange_ptr(
(void **)addr, PyStackRef_AsPyObjectSteal(value));
#else
PyObject *old_value = *(PyObject **)addr;
FT_ATOMIC_STORE_PTR_RELEASE(*(PyObject **)addr, PyStackRef_AsPyObjectSteal(value));
UNLOCK_OBJECT(owner_o);
INPUTS_DEAD();
*(PyObject **)addr = PyStackRef_AsPyObjectSteal(value);
#endif
o = owner;
DEAD(owner);
Py_XDECREF(old_value);
}

Expand Down
26 changes: 15 additions & 11 deletions Python/executor_cases.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 8 additions & 7 deletions Python/generated_cases.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

57 changes: 32 additions & 25 deletions Python/structmember.c
Original file line number Diff line number Diff line change
Expand Up @@ -97,36 +97,33 @@ PyMember_GetOne(const char *obj_addr, PyMemberDef *l)
break;
}
case _Py_T_OBJECT:
v = FT_ATOMIC_LOAD_PTR(*(PyObject **) addr);
if (v != NULL) {
#ifdef Py_GIL_DISABLED
if (!_Py_TryIncrefCompare((PyObject **) addr, v)) {
Py_BEGIN_CRITICAL_SECTION((PyObject *) obj_addr);
v = FT_ATOMIC_LOAD_PTR(*(PyObject **) addr);
Py_XINCREF(v);
Py_END_CRITICAL_SECTION();
}
// _Py_XGetRef does an atomic load + try-incref loop, retrying when
// a concurrent writer (lock-free atomic exchange in _STORE_ATTR_SLOT
// and PyMember_SetOne) mutates the slot. The previous critical-
// section fallback assumed writers also took ob_mutex, which they no
// longer do, so a plain Py_XINCREF inside the CS could resurrect a
// refcount-0 (about-to-be-freed) object.
v = _Py_XGetRef((PyObject **)addr);
#else
Py_INCREF(v);
v = *(PyObject **)addr;
Py_XINCREF(v);
#endif
}
if (v == NULL) {
v = Py_None;
v = Py_NewRef(Py_None);
}
break;
case Py_T_OBJECT_EX:
#ifdef Py_GIL_DISABLED
v = _Py_XGetRef((PyObject **)addr);
if (v == NULL) {
PyErr_Format(PyExc_AttributeError,
"'%T' object has no attribute '%s'",
(PyObject *)obj_addr, l->name);
}
#else
v = member_get_object(addr, obj_addr, l);
#ifndef Py_GIL_DISABLED
Py_XINCREF(v);
#else
if (v != NULL) {
if (!_Py_TryIncrefCompare((PyObject **) addr, v)) {
Py_BEGIN_CRITICAL_SECTION((PyObject *) obj_addr);
v = member_get_object(addr, obj_addr, l);
Py_XINCREF(v);
Py_END_CRITICAL_SECTION();
}
}
#endif
break;
case Py_T_LONGLONG:
Expand Down Expand Up @@ -320,11 +317,20 @@ PyMember_SetOne(char *addr, PyMemberDef *l, PyObject *v)
break;
}
case _Py_T_OBJECT:
case Py_T_OBJECT_EX:
Py_BEGIN_CRITICAL_SECTION(obj);
case Py_T_OBJECT_EX: {
// Lock-free swap to match the _STORE_ATTR_SLOT bytecode handler
// (which also uses atomic exchange on Py_GIL_DISABLED builds).
// Atomic exchange gives each concurrent writer a unique old value,
// so the Py_XDECREF below cannot double-free. Concurrent readers
// (PyMember_GetOne, _LOAD_ATTR_SLOT) use atomic loads and the
// TryIncrefCompare pattern to cope with mid-flight mutations.
PyObject *new_v = Py_XNewRef(v);
#ifdef Py_GIL_DISABLED
oldv = (PyObject *)_Py_atomic_exchange_ptr((void **)addr, new_v);
#else
oldv = *(PyObject **)addr;
FT_ATOMIC_STORE_PTR_RELEASE(*(PyObject **)addr, Py_XNewRef(v));
Py_END_CRITICAL_SECTION();
*(PyObject **)addr = new_v;
#endif
if (v == NULL && oldv == NULL && l->type == Py_T_OBJECT_EX) {
// Raise an exception when attempting to delete an already deleted
// attribute.
Expand All @@ -336,6 +342,7 @@ PyMember_SetOne(char *addr, PyMemberDef *l, PyObject *v)
}
Py_XDECREF(oldv);
break;
}
case Py_T_CHAR: {
const char *string;
Py_ssize_t len;
Expand Down