Skip to content

Commit 91e82d6

Browse files
committed
Cleanup _PyObject_SetManagedDict, use cst for inline valid flag, fix issue w/ deleted dict
1 parent 6d37f80 commit 91e82d6

File tree

4 files changed

+93
-56
lines changed

4 files changed

+93
-56
lines changed

Include/internal/pycore_pyatomic_ft_wrappers.h

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ extern "C" {
2121

2222
#ifdef Py_GIL_DISABLED
2323
#define FT_ATOMIC_LOAD_PTR(value) _Py_atomic_load_ptr(&value)
24+
#define FT_ATOMIC_STORE_PTR(value, new_value) _Py_atomic_store_ptr(&value, new_value)
2425
#define FT_ATOMIC_LOAD_SSIZE(value) _Py_atomic_load_ssize(&value)
2526
#define FT_ATOMIC_LOAD_SSIZE_ACQUIRE(value) \
2627
_Py_atomic_load_ssize_acquire(&value)
@@ -30,10 +31,10 @@ extern "C" {
3031
_Py_atomic_store_ptr(&value, new_value)
3132
#define FT_ATOMIC_LOAD_PTR_RELAXED(value) \
3233
_Py_atomic_load_ptr_relaxed(&value)
33-
#define FT_ATOMIC_LOAD_UINT8_RELAXED(value) \
34-
_Py_atomic_load_uint8_relaxed(&value)
35-
#define FT_ATOMIC_STORE_UINT8_RELAXED(value, new_value) \
36-
_Py_atomic_store_uint8_relaxed(&value, new_value)
34+
#define FT_ATOMIC_LOAD_UINT8(value) \
35+
_Py_atomic_load_uint8(&value)
36+
#define FT_ATOMIC_STORE_UINT8(value, new_value) \
37+
_Py_atomic_store_uint8(&value, new_value)
3738
#define FT_ATOMIC_STORE_PTR_RELAXED(value, new_value) \
3839
_Py_atomic_store_ptr_relaxed(&value, new_value)
3940
#define FT_ATOMIC_STORE_PTR_RELEASE(value, new_value) \
@@ -42,13 +43,14 @@ extern "C" {
4243
_Py_atomic_store_ssize_relaxed(&value, new_value)
4344
#else
4445
#define FT_ATOMIC_LOAD_PTR(value) value
46+
#define FT_ATOMIC_STORE_PTR(value, new_value) value = new_value
4547
#define FT_ATOMIC_LOAD_SSIZE(value) value
4648
#define FT_ATOMIC_LOAD_SSIZE_ACQUIRE(value) value
4749
#define FT_ATOMIC_LOAD_SSIZE_RELAXED(value) value
4850
#define FT_ATOMIC_STORE_PTR(value, new_value) value = new_value
4951
#define FT_ATOMIC_LOAD_PTR_RELAXED(value) value
50-
#define FT_ATOMIC_LOAD_UINT8_RELAXED(value) value
51-
#define FT_ATOMIC_STORE_UINT8_RELAXED(value, new_value) value = new_value
52+
#define FT_ATOMIC_LOAD_UINT8(value) value
53+
#define FT_ATOMIC_STORE_UINT8(value, new_value) value = new_value
5254
#define FT_ATOMIC_STORE_PTR_RELAXED(value, new_value) value = new_value
5355
#define FT_ATOMIC_STORE_PTR_RELEASE(value, new_value) value = new_value
5456
#define FT_ATOMIC_STORE_SSIZE_RELAXED(value, new_value) value = new_value

Lib/test/test_class.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -873,6 +873,15 @@ def __init__(self):
873873
obj.foo = None # Aborted here
874874
self.assertEqual(obj.__dict__, {"foo":None})
875875

876+
def test_store_attr_deleted_dict(self):
877+
class Foo:
878+
pass
879+
880+
f = Foo()
881+
del f.__dict__
882+
f.a = 3
883+
self.assertEqual(f.a, 3)
884+
876885

877886
if __name__ == '__main__':
878887
unittest.main()

Objects/dictobject.c

Lines changed: 65 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -6796,8 +6796,15 @@ int
67966796
_PyObject_StoreInstanceAttribute(PyObject *obj, PyObject *name, PyObject *value)
67976797
{
67986798
PyDictValues *values = _PyObject_InlineValues(obj);
6799-
if (!FT_ATOMIC_LOAD_UINT8_RELAXED(values->valid)) {
6800-
return store_instance_attr_dict(obj, _PyObject_GetManagedDict(obj), name, value);
6799+
if (!FT_ATOMIC_LOAD_UINT8(values->valid)) {
6800+
PyDictObject *dict = _PyObject_GetManagedDict(obj);
6801+
if (dict == NULL) {
6802+
dict = (PyDictObject *)PyObject_GenericGetDict(obj, NULL);
6803+
if (dict == NULL) {
6804+
return -1;
6805+
}
6806+
}
6807+
return store_instance_attr_dict(obj, dict, name, value);
68016808
}
68026809

68036810
#ifdef Py_GIL_DISABLED
@@ -6871,7 +6878,7 @@ _PyObject_TryGetInstanceAttribute(PyObject *obj, PyObject *name, PyObject **attr
68716878
{
68726879
assert(PyUnicode_CheckExact(name));
68736880
PyDictValues *values = _PyObject_InlineValues(obj);
6874-
if (!FT_ATOMIC_LOAD_UINT8_RELAXED(values->valid)) {
6881+
if (!FT_ATOMIC_LOAD_UINT8(values->valid)) {
68756882
return false;
68766883
}
68776884

@@ -6920,8 +6927,7 @@ _PyObject_TryGetInstanceAttribute(PyObject *obj, PyObject *name, PyObject **attr
69206927
bool success;
69216928
Py_BEGIN_CRITICAL_SECTION(dict);
69226929

6923-
if (dict->ma_values == values &&
6924-
FT_ATOMIC_LOAD_UINT8_RELAXED(values->valid)) {
6930+
if (dict->ma_values == values && FT_ATOMIC_LOAD_UINT8(values->valid)) {
69256931
value = _Py_atomic_load_ptr_relaxed(&values->values[ix]);
69266932
*attr = Py_XNewRef(value);
69276933
success = true;
@@ -6950,7 +6956,7 @@ _PyObject_IsInstanceDictEmpty(PyObject *obj)
69506956
PyDictObject *dict;
69516957
if (tp->tp_flags & Py_TPFLAGS_INLINE_VALUES) {
69526958
PyDictValues *values = _PyObject_InlineValues(obj);
6953-
if (FT_ATOMIC_LOAD_UINT8_RELAXED(values->valid)) {
6959+
if (FT_ATOMIC_LOAD_UINT8(values->valid)) {
69546960
PyDictKeysObject *keys = CACHED_KEYS(tp);
69556961
for (Py_ssize_t i = 0; i < keys->dk_nentries; i++) {
69566962
if (FT_ATOMIC_LOAD_PTR_RELAXED(values->values[i]) != NULL) {
@@ -6994,32 +7000,22 @@ PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg)
69947000
return 0;
69957001
}
69967002

6997-
static bool
6998-
set_dict_inline_values(PyObject *obj, PyObject *new_dict)
7003+
static void
7004+
set_dict_inline_values(PyObject *obj, PyDictObject *new_dict)
69997005
{
70007006
_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(obj);
70017007

70027008
PyDictValues *values = _PyObject_InlineValues(obj);
70037009

7004-
#ifdef Py_GIL_DISABLED
7005-
PyDictObject *dict = _PyObject_ManagedDictPointer(obj)->dict;
7006-
if (dict != NULL) {
7007-
// We lost a race with materialization of the dict
7008-
return false;
7009-
}
7010-
#endif
7011-
70127010
Py_XINCREF(new_dict);
7013-
_PyObject_ManagedDictPointer(obj)->dict = (PyDictObject *)new_dict;
7011+
FT_ATOMIC_STORE_PTR(_PyObject_ManagedDictPointer(obj)->dict, new_dict);
70147012

70157013
if (values->valid) {
7016-
FT_ATOMIC_STORE_UINT8_RELAXED(values->valid, 0);
7014+
FT_ATOMIC_STORE_UINT8(values->valid, 0);
70177015
for (Py_ssize_t i = 0; i < values->capacity; i++) {
70187016
Py_CLEAR(values->values[i]);
70197017
}
70207018
}
7021-
7022-
return true;
70237019
}
70247020

70257021
void
@@ -7030,47 +7026,67 @@ _PyObject_SetManagedDict(PyObject *obj, PyObject *new_dict)
70307026
PyTypeObject *tp = Py_TYPE(obj);
70317027
if (tp->tp_flags & Py_TPFLAGS_INLINE_VALUES) {
70327028
PyDictObject *dict = _PyObject_GetManagedDict(obj);
7033-
if (dict) {
7029+
if (dict == NULL) {
70347030
#ifdef Py_GIL_DISABLED
7035-
clear_dict:
7036-
#endif
7037-
Py_BEGIN_CRITICAL_SECTION2(dict, obj);
7038-
7039-
_PyDict_DetachFromObject(dict, obj);
7031+
Py_BEGIN_CRITICAL_SECTION(obj);
70407032

7041-
Py_XINCREF(new_dict);
7042-
_PyObject_ManagedDictPointer(obj)->dict = (PyDictObject *)new_dict;
7033+
dict = _PyObject_ManagedDictPointer(obj)->dict;
7034+
if (dict == NULL) {
7035+
set_dict_inline_values(obj, (PyDictObject *)new_dict);
7036+
}
70437037

7044-
Py_END_CRITICAL_SECTION2();
7038+
Py_END_CRITICAL_SECTION();
70457039

7046-
Py_DECREF(dict);
7040+
if (dict == NULL) {
7041+
return;
7042+
}
7043+
#else
7044+
set_dict_inline_values(obj, (PyDictObject *)new_dict);
7045+
return;
7046+
#endif
70477047
}
7048-
else {
7049-
bool success;
7050-
Py_BEGIN_CRITICAL_SECTION(obj);
70517048

7052-
success = set_dict_inline_values(obj, new_dict);
7049+
Py_BEGIN_CRITICAL_SECTION2(dict, obj);
70537050

7054-
Py_END_CRITICAL_SECTION();
7051+
// If the dict in the object has been replaced between when we
7052+
// got the dict and unlocked the objects then it's
7053+
// definitely no longer inline and there's no need to detach
7054+
// it, we can just replace it.
7055+
PyDictObject *cur_dict = _PyObject_ManagedDictPointer(obj)->dict;
7056+
assert(cur_dict == dict ||
7057+
(cur_dict->ma_values != _PyObject_InlineValues(obj) &&
7058+
!_PyObject_InlineValues(obj)->valid));
70557059

7056-
(void)success; // suppress warning when GIL isn't disabled
7060+
Py_XINCREF(new_dict);
7061+
FT_ATOMIC_STORE_PTR(_PyObject_ManagedDictPointer(obj)->dict,
7062+
(PyDictObject *)Py_XNewRef(new_dict));
70577063

7058-
#ifdef Py_GIL_DISABLED
7059-
if (!success) {
7060-
dict = _PyObject_ManagedDictPointer(obj)->dict;
7061-
assert(dict != NULL);
7062-
goto clear_dict;
7063-
}
7064-
#endif
7064+
// If we got a replacement dict after locking the object and the dict
7065+
// then the old dict had to already have been detached.
7066+
assert(cur_dict == dict ||
7067+
dict->ma_values != _PyObject_InlineValues(obj));
7068+
7069+
if (cur_dict == dict) {
7070+
_PyDict_DetachFromObject(dict, obj);
70657071
}
7072+
7073+
Py_END_CRITICAL_SECTION2();
7074+
7075+
Py_DECREF(dict);
70667076
}
70677077
else {
7078+
PyDictObject *dict;
7079+
70687080
Py_BEGIN_CRITICAL_SECTION(obj);
70697081

7070-
Py_XSETREF(_PyObject_ManagedDictPointer(obj)->dict,
7071-
(PyDictObject *)Py_XNewRef(new_dict));
7082+
dict = _PyObject_ManagedDictPointer(obj)->dict;
7083+
7084+
FT_ATOMIC_STORE_PTR(_PyObject_ManagedDictPointer(obj)->dict,
7085+
(PyDictObject *)Py_XNewRef(new_dict));
70727086

70737087
Py_END_CRITICAL_SECTION();
7088+
7089+
Py_XDECREF(dict);
70747090
}
70757091
assert(_PyObject_InlineValuesConsistencyCheck(obj));
70767092
}
@@ -7085,9 +7101,8 @@ int
70857101
_PyDict_DetachFromObject(PyDictObject *mp, PyObject *obj)
70867102
{
70877103
_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(mp);
7104+
_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(obj);
70887105

7089-
assert(_PyObject_ManagedDictPointer(obj)->dict == mp);
7090-
assert(_PyObject_InlineValuesConsistencyCheck(obj));
70917106
if (mp->ma_values == NULL || mp->ma_values != _PyObject_InlineValues(obj)) {
70927107
return 0;
70937108
}
@@ -7096,12 +7111,13 @@ _PyDict_DetachFromObject(PyDictObject *mp, PyObject *obj)
70967111
assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_INLINE_VALUES);
70977112

70987113
mp->ma_values = copy_values(mp->ma_values);
7099-
_PyObject_InlineValues(obj)->valid = 0;
71007114

71017115
if (mp->ma_values == NULL) {
71027116
return -1;
71037117
}
71047118

7119+
FT_ATOMIC_STORE_UINT8(_PyObject_InlineValues(obj)->valid, 0);
7120+
71057121
assert(_PyObject_InlineValuesConsistencyCheck(obj));
71067122
ASSERT_CONSISTENT(mp);
71077123
return 0;
@@ -7117,7 +7133,7 @@ PyObject_GenericGetDict(PyObject *obj, void *context)
71177133
dict = _PyObject_GetManagedDict(obj);
71187134
if (dict == NULL &&
71197135
(tp->tp_flags & Py_TPFLAGS_INLINE_VALUES) &&
7120-
FT_ATOMIC_LOAD_UINT8_RELAXED(_PyObject_InlineValues(obj)->valid)) {
7136+
FT_ATOMIC_LOAD_UINT8(_PyObject_InlineValues(obj)->valid)) {
71217137
dict = _PyObject_MaterializeManagedDict(obj);
71227138
}
71237139
else if (dict == NULL) {

Objects/typeobject.c

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6195,11 +6195,21 @@ object_set_class(PyObject *self, PyObject *value, void *closure)
61956195
* so we must materialize the dictionary first. */
61966196
if (oldto->tp_flags & Py_TPFLAGS_INLINE_VALUES) {
61976197
PyDictObject *dict = _PyObject_MaterializeManagedDict(self);
6198+
if (dict == NULL) {
6199+
return -1;
6200+
}
6201+
61986202
bool error = false;
61996203

62006204
Py_BEGIN_CRITICAL_SECTION2(self, dict);
62016205

6202-
if (dict == NULL || _PyDict_DetachFromObject(dict, self) < 0) {
6206+
// If we raced after materialization and replaced the dict
6207+
// then the materialized dict should no longer have the
6208+
// inline values in which case detach is a nop.
6209+
assert(_PyObject_GetManagedDict(self) == dict ||
6210+
dict->ma_values != _PyObject_InlineValues(self));
6211+
6212+
if (_PyDict_DetachFromObject(dict, self) < 0) {
62036213
error = true;
62046214
}
62056215

0 commit comments

Comments
 (0)