Skip to content

slot.__delete__() behaves as non-atomic in free-threading #146270

Description

@x42005e1f

Bug report

Bug description:

In #119368+#123211, member descriptors (related to user-defined slots) were made thread-safe. But not everywhere. The branch related to deletion remained unchanged:

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;
}
}
elseif (l->type!=_Py_T_OBJECT) {
PyErr_SetString(PyExc_TypeError,
"can't delete numeric/char attribute");
return-1;
}
}

Since threads perform the check (addr == NULL) in a non-thread-safe manner, the operation may succeed even if it has already been executed by another thread. At the Python level, this means that an AttributeError may not be raised during two or more concurrent attempts to delete an attribute, and as a result, code that was supposed to execute only once (after a successful deletion) will be executed more than once.

The behavior matches what is expected on CPython with GIL and on PyPy (and possibly other interpreters), but not on CPython without GIL. Without slots (using __dict__), the behavior also matches what is expected even in free-threading. Meanwhile, user-defined slots already rely on critical sections, so the simplest solution would be to move the check for Py_T_OBJECT_EX under the critical section (into the case block).

Code to reproduce (del obj.b raises an AttributeError):

#!/usr/bin/env python3importtimefromconcurrent.futuresimportThreadPoolExecutorclassObjectWithSlots:
__slots__= (
"a",
"b",
)
def__init__(self):
self.a=Noneself.b=Nonedefmain():
deftest():
try:
delobj.aexceptAttributeError: # not the first threadpasselse:
delobj.bwithThreadPoolExecutor(2) asexecutor:
start=time.perf_counter()
whiletime.perf_counter() -start<1: # one secondobj=ObjectWithSlots()
f1=executor.submit(test)
f2=executor.submit(test)
f1.result() # reraisef2.result() # reraiseif__name__=="__main__":
main()

CPython versions tested on:

3.14

Operating systems tested on:

Linux

Linked PRs

Metadata

Metadata

Assignees

Labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions