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
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()
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:{
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: {