https://github.com/python/cpython/commit/1bdfc0f253730077ccd3a4b0714388e8227b1b71
commit: 1bdfc0f253730077ccd3a4b0714388e8227b1b71
branch: main
author: Daniele Parmeggiani <[email protected]>
committer: colesbury <[email protected]>
date: 2026-05-06T09:50:24-04:00
summary:
gh-146270: Fix `PyMember_SetOne(..., NULL)` not being atomic (gh-148800)
Fixes a sequential consistency bug whereby two threads that are deleting a
struct member may observe both their deletions to be successful.
files:
A
Misc/NEWS.d/next/Core_and_Builtins/2026-04-20-15-25-55.gh-issue-146270.qZYfyc.rst
M Lib/test/test_free_threading/test_slots.py
M Python/structmember.c
diff --git a/Lib/test/test_free_threading/test_slots.py
b/Lib/test/test_free_threading/test_slots.py
index a3b9f4b0175ae7..a73525e1bebfb4 100644
--- a/Lib/test/test_free_threading/test_slots.py
+++ b/Lib/test/test_free_threading/test_slots.py
@@ -16,18 +16,19 @@ def run_in_threads(targets):
thread.join()
+class Spam:
+ __slots__ = [
+ "eggs",
+ ]
+
+ def __init__(self, initial_value):
+ self.eggs = initial_value
+
+
@threading_helper.requires_working_threading()
class TestSlots(TestCase):
def test_object(self):
- class Spam:
- __slots__ = [
- "eggs",
- ]
-
- def __init__(self, initial_value):
- self.eggs = initial_value
-
spam = Spam(0)
iters = 20_000
@@ -43,6 +44,24 @@ def reader():
run_in_threads([writer, reader, reader, reader])
+ def test_del_object_is_atomic(self):
+ # Testing whether the implementation of `del slots_object.attribute`
+ # removes the attribute atomically, thus avoiding non-sequentially-
+ # consistent behaviors.
+ # https://github.com/python/cpython/issues/146270
+ def deleter(spam, successes):
+ try:
+ del spam.eggs
+ successes.append(True)
+ except AttributeError:
+ successes.append(False)
+
+ for _ in range(10):
+ spam = Spam(0)
+ successes = []
+ threading_helper.run_concurrently(deleter, nthreads=4, args=(spam,
successes))
+ self.assertEqual(sum(successes), 1)
+
def test_T_BOOL(self):
spam_old = _testcapi._test_structmembersType_OldAPI()
spam_new = _testcapi._test_structmembersType_NewAPI()
diff --git
a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-20-15-25-55.gh-issue-146270.qZYfyc.rst
b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-20-15-25-55.gh-issue-146270.qZYfyc.rst
new file mode 100644
index 00000000000000..46c292e183e0fd
--- /dev/null
+++
b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-20-15-25-55.gh-issue-146270.qZYfyc.rst
@@ -0,0 +1 @@
+Fix a sequential consistency bug in ``structmember.c``.
diff --git a/Python/structmember.c b/Python/structmember.c
index b88e13ac0462b8..adea8216b8796b 100644
--- a/Python/structmember.c
+++ b/Python/structmember.c
@@ -171,19 +171,10 @@ PyMember_SetOne(char *addr, PyMemberDef *l, PyObject *v)
PyErr_SetString(PyExc_AttributeError, "readonly attribute");
return -1;
}
- if (v == NULL) {
- if (l->type == Py_T_OBJECT_EX) {
- /* Check if the attribute is set. */
- if (*(PyObject **)addr == NULL) {
- PyErr_SetString(PyExc_AttributeError, l->name);
- return -1;
- }
- }
- else if (l->type != _Py_T_OBJECT) {
- PyErr_SetString(PyExc_TypeError,
- "can't delete numeric/char attribute");
- return -1;
- }
+ if (v == NULL && l->type != Py_T_OBJECT_EX && l->type != _Py_T_OBJECT) {
+ PyErr_SetString(PyExc_TypeError,
+ "can't delete numeric/char attribute");
+ return -1;
}
switch (l->type) {
case Py_T_BOOL:{
@@ -334,6 +325,15 @@ PyMember_SetOne(char *addr, PyMemberDef *l, PyObject *v)
oldv = *(PyObject **)addr;
FT_ATOMIC_STORE_PTR_RELEASE(*(PyObject **)addr, Py_XNewRef(v));
Py_END_CRITICAL_SECTION();
+ if (v == NULL && oldv == NULL && l->type == Py_T_OBJECT_EX) {
+ // Raise an exception when attempting to delete an already deleted
+ // attribute.
+ // Differently from Py_T_OBJECT_EX, _Py_T_OBJECT does not raise an
+ // exception here (PyMember_GetOne will return Py_None instead of
+ // NULL).
+ PyErr_SetString(PyExc_AttributeError, l->name);
+ return -1;
+ }
Py_XDECREF(oldv);
break;
case Py_T_CHAR: {
_______________________________________________
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]