@@ -7087,51 +7087,146 @@ set_dict_inline_values(PyObject *obj, PyDictObject *new_dict)
7087
7087
}
7088
7088
}
7089
7089
7090
- int
7091
- _PyObject_SetManagedDict (PyObject * obj , PyObject * new_dict )
7090
+ #ifdef Py_GIL_DISABLED
7091
+
7092
+ // Trys and sets the dictionary for an object in the easy case when our current
7093
+ // dictionary is either completely not materialized or is a dictionary which
7094
+ // does not point at the inline values.
7095
+ static bool
7096
+ try_set_dict_inline_only_or_other_dict (PyObject * obj , PyObject * new_dict , PyDictObject * * cur_dict )
7097
+ {
7098
+ bool replaced = false;
7099
+ Py_BEGIN_CRITICAL_SECTION (obj );
7100
+
7101
+ PyDictObject * dict = * cur_dict = _PyObject_GetManagedDict (obj );
7102
+ if (dict == NULL ) {
7103
+ // We only have inline values, we can just completely replace them.
7104
+ set_dict_inline_values (obj , (PyDictObject * )new_dict );
7105
+ replaced = true;
7106
+ goto exit_lock ;
7107
+ }
7108
+
7109
+ if (FT_ATOMIC_LOAD_PTR_RELAXED (dict -> ma_values ) != _PyObject_InlineValues (obj )) {
7110
+ // We have a materialized dict which doesn't point at the inline values,
7111
+ // We get to simply swap dictionaries and free the old dictionary.
7112
+ FT_ATOMIC_STORE_PTR (_PyObject_ManagedDictPointer (obj )-> dict ,
7113
+ (PyDictObject * )Py_XNewRef (new_dict ));
7114
+ replaced = true;
7115
+ goto exit_lock ;
7116
+ }
7117
+ else {
7118
+ // We have inline values, we need to lock the dict and the object
7119
+ // at the same time to safely dematerialize them. To do that while releasing
7120
+ // the object lock we need a strong reference to the current dictionary.
7121
+ Py_INCREF (dict );
7122
+ }
7123
+ exit_lock :
7124
+ Py_END_CRITICAL_SECTION ();
7125
+ return replaced ;
7126
+ }
7127
+
7128
+ // Replaces a dictionary that is probably the dictionary which has been
7129
+ // materialized and points at the inline values. We could have raced
7130
+ // and replaced it with another dictionary though.
7131
+ static int
7132
+ replace_dict_probably_inline_materialized (PyObject * obj , PyDictObject * inline_dict ,
7133
+ PyDictObject * cur_dict , PyObject * new_dict )
7134
+ {
7135
+ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED (obj );
7136
+
7137
+ if (cur_dict == inline_dict ) {
7138
+ assert (FT_ATOMIC_LOAD_PTR_RELAXED (inline_dict -> ma_values ) == _PyObject_InlineValues (obj ));
7139
+
7140
+ int err = _PyDict_DetachFromObject (inline_dict , obj );
7141
+ if (err != 0 ) {
7142
+ assert (new_dict == NULL );
7143
+ return err ;
7144
+ }
7145
+ }
7146
+
7147
+ FT_ATOMIC_STORE_PTR (_PyObject_ManagedDictPointer (obj )-> dict ,
7148
+ (PyDictObject * )Py_XNewRef (new_dict ));
7149
+ return 0 ;
7150
+ }
7151
+
7152
+ #endif
7153
+
7154
+ static void
7155
+ decref_maybe_delay (PyObject * obj , bool delay )
7156
+ {
7157
+ if (delay ) {
7158
+ _PyObject_XDecRefDelayed (obj );
7159
+ }
7160
+ else {
7161
+ Py_XDECREF (obj );
7162
+ }
7163
+ }
7164
+
7165
+ static int
7166
+ set_or_clear_managed_dict (PyObject * obj , PyObject * new_dict , bool clear )
7092
7167
{
7093
7168
assert (Py_TYPE (obj )-> tp_flags & Py_TPFLAGS_MANAGED_DICT );
7169
+ #ifndef NDEBUG
7170
+ Py_BEGIN_CRITICAL_SECTION (obj );
7094
7171
assert (_PyObject_InlineValuesConsistencyCheck (obj ));
7172
+ Py_END_CRITICAL_SECTION ();
7173
+ #endif
7095
7174
int err = 0 ;
7096
7175
PyTypeObject * tp = Py_TYPE (obj );
7097
7176
if (tp -> tp_flags & Py_TPFLAGS_INLINE_VALUES ) {
7098
- PyDictObject * dict = _PyObject_GetManagedDict (obj );
7099
- if (dict == NULL ) {
7100
7177
#ifdef Py_GIL_DISABLED
7101
- Py_BEGIN_CRITICAL_SECTION (obj );
7178
+ PyDictObject * prev_dict ;
7179
+ if (!try_set_dict_inline_only_or_other_dict (obj , new_dict , & prev_dict )) {
7180
+ // We had a materialized dictionary which pointed at the inline
7181
+ // values. We need to lock both the object and the dict at the
7182
+ // same time to safely replace it. We can't merely lock the dictionary
7183
+ // while the object is locked because it could suspend the object lock.
7184
+ PyDictObject * cur_dict ;
7102
7185
7103
- dict = _PyObject_ManagedDictPointer (obj )-> dict ;
7104
- if (dict == NULL ) {
7105
- set_dict_inline_values (obj , (PyDictObject * )new_dict );
7106
- }
7186
+ assert (prev_dict != NULL );
7187
+ Py_BEGIN_CRITICAL_SECTION2 (obj , prev_dict );
7107
7188
7108
- Py_END_CRITICAL_SECTION ();
7189
+ // We could have had another thread race in between the call to
7190
+ // try_set_dict_inline_only_or_other_dict where we locked the object
7191
+ // and when we unlocked and re-locked the dictionary.
7192
+ cur_dict = _PyObject_GetManagedDict (obj );
7109
7193
7110
- if (dict == NULL ) {
7111
- return 0 ;
7194
+ err = replace_dict_probably_inline_materialized (obj , prev_dict ,
7195
+ cur_dict , new_dict );
7196
+
7197
+ Py_END_CRITICAL_SECTION2 ();
7198
+
7199
+ // Decref for the dictionary we incref'd in try_set_dict_inline_only_or_other_dict
7200
+ // while the object was locked
7201
+ decref_maybe_delay ((PyObject * )prev_dict ,
7202
+ !clear && prev_dict != cur_dict );
7203
+ if (err != 0 ) {
7204
+ return err ;
7112
7205
}
7113
- #else
7114
- set_dict_inline_values (obj , (PyDictObject * )new_dict );
7115
- return 0 ;
7116
- #endif
7117
- }
7118
7206
7119
- Py_BEGIN_CRITICAL_SECTION2 (dict , obj );
7207
+ prev_dict = cur_dict ;
7208
+ }
7120
7209
7121
- // We've locked dict, but the actual dict could have changed
7122
- // since we locked it.
7123
- dict = _PyObject_ManagedDictPointer (obj )-> dict ;
7124
- err = _PyDict_DetachFromObject (dict , obj );
7125
- assert (err == 0 || new_dict == NULL );
7126
- if (err == 0 ) {
7127
- FT_ATOMIC_STORE_PTR (_PyObject_ManagedDictPointer (obj )-> dict ,
7128
- (PyDictObject * )Py_XNewRef (new_dict ));
7210
+ if (prev_dict != NULL ) {
7211
+ // decref for the dictionary that we replaced
7212
+ decref_maybe_delay ((PyObject * )prev_dict , !clear );
7129
7213
}
7130
- Py_END_CRITICAL_SECTION2 ();
7131
7214
7132
- if (err == 0 ) {
7133
- Py_XDECREF (dict );
7215
+ return 0 ;
7216
+ #else
7217
+ PyDictObject * dict = _PyObject_GetManagedDict (obj );
7218
+ if (dict == NULL ) {
7219
+ set_dict_inline_values (obj , (PyDictObject * )new_dict );
7220
+ return 0 ;
7221
+ }
7222
+ if (_PyDict_DetachFromObject (dict , obj ) == 0 ) {
7223
+ _PyObject_ManagedDictPointer (obj )-> dict = (PyDictObject * )Py_XNewRef (new_dict );
7224
+ Py_DECREF (dict );
7225
+ return 0 ;
7134
7226
}
7227
+ assert (new_dict == NULL );
7228
+ return -1 ;
7229
+ #endif
7135
7230
}
7136
7231
else {
7137
7232
PyDictObject * dict ;
@@ -7144,17 +7239,22 @@ _PyObject_SetManagedDict(PyObject *obj, PyObject *new_dict)
7144
7239
(PyDictObject * )Py_XNewRef (new_dict ));
7145
7240
7146
7241
Py_END_CRITICAL_SECTION ();
7147
-
7148
- Py_XDECREF (dict );
7242
+ decref_maybe_delay ((PyObject * )dict , !clear );
7149
7243
}
7150
7244
assert (_PyObject_InlineValuesConsistencyCheck (obj ));
7151
7245
return err ;
7152
7246
}
7153
7247
7248
+ int
7249
+ _PyObject_SetManagedDict (PyObject * obj , PyObject * new_dict )
7250
+ {
7251
+ return set_or_clear_managed_dict (obj , new_dict , false);
7252
+ }
7253
+
7154
7254
void
7155
7255
PyObject_ClearManagedDict (PyObject * obj )
7156
7256
{
7157
- if (_PyObject_SetManagedDict (obj , NULL ) < 0 ) {
7257
+ if (set_or_clear_managed_dict (obj , NULL , true ) < 0 ) {
7158
7258
/* Must be out of memory */
7159
7259
assert (PyErr_Occurred () == PyExc_MemoryError );
7160
7260
PyErr_WriteUnraisable (NULL );
0 commit comments