@@ -165,6 +165,8 @@ ASSERT_DICT_LOCKED(PyObject *op)
165
165
166
166
#define IS_DICT_SHARED (mp ) _PyObject_GC_IS_SHARED(mp)
167
167
#define SET_DICT_SHARED (mp ) _PyObject_GC_SET_SHARED(mp)
168
+ #define IS_DICT_SHARED_INLINE (mp ) _PyObject_GC_IS_SHARED_INLINE(mp)
169
+ #define SET_DICT_SHARED_INLINE (mp ) _PyObject_GC_SET_SHARED_INLINE(mp)
168
170
#define LOAD_INDEX (keys , size , idx ) _Py_atomic_load_int##size##_relaxed(&((const int##size##_t*)keys->dk_indices)[idx]);
169
171
#define STORE_INDEX (keys , size , idx , value ) _Py_atomic_store_int##size##_relaxed(&((int##size##_t*)keys->dk_indices)[idx], (int##size##_t)value);
170
172
#define ASSERT_OWNED_OR_SHARED (mp ) \
@@ -245,6 +247,8 @@ static inline void split_keys_entry_added(PyDictKeysObject *keys)
245
247
#define UNLOCK_KEYS_IF_SPLIT (keys , kind )
246
248
#define IS_DICT_SHARED (mp ) (false)
247
249
#define SET_DICT_SHARED (mp )
250
+ #define IS_DICT_SHARED_INLINE (mp ) (false)
251
+ #define SET_DICT_SHARED_INLINE (mp )
248
252
#define LOAD_INDEX (keys , size , idx ) ((const int##size##_t*)(keys->dk_indices))[idx]
249
253
#define STORE_INDEX (keys , size , idx , value ) ((int##size##_t*)(keys->dk_indices))[idx] = (int##size##_t)value
250
254
@@ -3198,7 +3202,7 @@ dict_dealloc(PyObject *self)
3198
3202
assert (keys -> dk_refcnt == 1 || keys == Py_EMPTY_KEYS );
3199
3203
dictkeys_decref (interp , keys , false);
3200
3204
}
3201
- if (Py_IS_TYPE (mp , & PyDict_Type )) {
3205
+ if (Py_IS_TYPE (mp , & PyDict_Type ) && ! IS_DICT_SHARED_INLINE ( mp ) ) {
3202
3206
_Py_FREELIST_FREE (dicts , mp , Py_TYPE (mp )-> tp_free );
3203
3207
}
3204
3208
else {
@@ -7126,6 +7130,75 @@ set_dict_inline_values(PyObject *obj, PyDictObject *new_dict)
7126
7130
}
7127
7131
}
7128
7132
7133
+ #ifdef Py_GIL_DISABLED
7134
+
7135
+ // Trys and sets the dictionary for an object in the easy case when our current
7136
+ // dictionary is either completely not materialized or is a dictionary which
7137
+ // does not point at the inline values.
7138
+ static bool
7139
+ try_set_dict_inline_only_or_other_dict (PyObject * obj , PyObject * new_dict , PyDictObject * * cur_dict )
7140
+ {
7141
+ bool replaced = false;
7142
+ Py_BEGIN_CRITICAL_SECTION (obj );
7143
+
7144
+ PyDictObject * dict = * cur_dict = _PyObject_GetManagedDict (obj );
7145
+ if (dict == NULL ) {
7146
+ // We only have inline values, we can just completely replace them.
7147
+ set_dict_inline_values (obj , (PyDictObject * )new_dict );
7148
+ replaced = true;
7149
+ goto exit_lock ;
7150
+ }
7151
+
7152
+ if (FT_ATOMIC_LOAD_PTR_RELAXED (dict -> ma_values ) != _PyObject_InlineValues (obj )) {
7153
+ // We have a materialized dict which doesn't point at the inline values,
7154
+ // We get to simply swap dictionaries and free the old dictionary.
7155
+ FT_ATOMIC_STORE_PTR (_PyObject_ManagedDictPointer (obj )-> dict ,
7156
+ (PyDictObject * )Py_XNewRef (new_dict ));
7157
+ replaced = true;
7158
+ goto exit_lock ;
7159
+ } else {
7160
+ // We have inline values, we need to lock the dict and the object
7161
+ // at the same time to safely dematerialize them. To do that while releasing
7162
+ // the object lock we need a strong reference to the current dictionary.
7163
+ Py_INCREF (dict );
7164
+ }
7165
+ exit_lock :
7166
+ Py_END_CRITICAL_SECTION ();
7167
+ return replaced ;
7168
+ }
7169
+
7170
+ #endif
7171
+
7172
+ // Replaces a dictionary that is probably the dictionary which has been
7173
+ // materialized and points at the inline values. We could have raced
7174
+ // and replaced it with another dictionary though.
7175
+ static int
7176
+ replace_dict_probably_inline_materialized (PyObject * obj , PyDictObject * inline_dict ,
7177
+ PyObject * new_dict , PyDictObject * * replaced_dict )
7178
+ {
7179
+ // But we could have had another thread race in after we released
7180
+ // the object lock
7181
+ int err = 0 ;
7182
+ * replaced_dict = _PyObject_GetManagedDict (obj );
7183
+ assert (FT_ATOMIC_LOAD_PTR_RELAXED (inline_dict -> ma_values ) == _PyObject_InlineValues (obj ));
7184
+
7185
+ if (* replaced_dict == inline_dict ) {
7186
+ err = _PyDict_DetachFromObject (inline_dict , obj );
7187
+ if (err != 0 ) {
7188
+ return err ;
7189
+ }
7190
+ SET_DICT_SHARED_INLINE ((PyObject * )inline_dict );
7191
+ // We incref'd the inline dict and the object owns a ref.
7192
+ // Clear the object's reference, we'll clear the local
7193
+ // reference after releasing the lock.
7194
+ Py_CLEAR (* replaced_dict );
7195
+ }
7196
+
7197
+ FT_ATOMIC_STORE_PTR (_PyObject_ManagedDictPointer (obj )-> dict ,
7198
+ (PyDictObject * )Py_XNewRef (new_dict ));
7199
+ return err ;
7200
+ }
7201
+
7129
7202
int
7130
7203
_PyObject_SetManagedDict (PyObject * obj , PyObject * new_dict )
7131
7204
{
@@ -7134,42 +7207,51 @@ _PyObject_SetManagedDict(PyObject *obj, PyObject *new_dict)
7134
7207
int err
10000
span> = 0 ;
7135
7208
PyTypeObject * tp = Py_TYPE (obj );
7136
7209
if (tp -> tp_flags & Py_TPFLAGS_INLINE_VALUES ) {
7137
- PyDictObject * dict = _PyObject_GetManagedDict (obj );
7138
- if (dict == NULL ) {
7139
7210
#ifdef Py_GIL_DISABLED
7140
- Py_BEGIN_CRITICAL_SECTION (obj );
7211
+ PyDictObject * prev_dict ;
7212
+ if (!try_set_dict_inline_only_or_other_dict (obj , new_dict , & prev_dict )) {
7213
+ // We had a materialized dictionary which pointed at the inline
7214
+ // values. We need to lock both the object and the dict at the
7215
+ // same time to safely replace it. We can't merely lock the dictionary
7216
+ // while the object is locked because it could suspend the object lock.
7217
+ PyDictObject * replaced_dict ;
7141
7218
7142
- dict = _PyObject_ManagedDictPointer (obj )-> dict ;
7143
- if (dict == NULL ) {
7144
- set_dict_inline_values (obj , (PyDictObject * )new_dict );
7145
- }
7219
+ assert (prev_dict != NULL );
7220
+ Py_BEGIN_CRITICAL_SECTION2 (obj , prev_dict );
7146
7221
7147
- Py_END_CRITICAL_SECTION ( );
7222
+ err = replace_dict_probably_inline_materialized ( obj , prev_dict , new_dict , & replaced_dict );
7148
7223
7149
- if (dict == NULL ) {
7150
- return 0 ;
7224
+ Py_END_CRITICAL_SECTION2 ();
7225
+
7226
+ Py_DECREF (prev_dict );
7227
+ if (err != 0 ) {
7228
+ return err ;
7151
7229
}
7230
+ prev_dict = replaced_dict ;
7231
+ }
7232
+
7233
+ if (prev_dict != NULL ) {
7234
+ Py_BEGIN_CRITICAL_SECTION (prev_dict );
7235
+ SET_DICT_SHARED_INLINE ((PyObject * )prev_dict );
7236
+ Py_END_CRITICAL_SECTION ();
7237
+ // Readers from the old dictionary use a borrowed reference. We need
7238
+ // to set the dict to be freed via QSBR which requires locking it.
7239
+ Py_DECREF (prev_dict );
7240
+ }
7241
+ return 0 ;
7152
7242
#else
7243
+ PyDictObject * dict = _PyObject_GetManagedDict (obj );
7244
+ if (dict == NULL ) {
7153
7245
set_dict_inline_values (obj , (PyDictObject * )new_dict );
7154
7246
return 0 ;
7155
- #endif
7156
- }
7157
-
7158
- Py_BEGIN_CRITICAL_SECTION2 (dict , obj );
7159
-
7160
- // We've locked dict, but the actual dict could have changed
7161
- // since we locked it.
7162
- dict = _PyObject_ManagedDictPointer (obj )-> dict ;
7163
- err = _PyDict_DetachFromObject (dict , obj );
7164
- if (err == 0 ) {
7165
- FT_ATOMIC_STORE_PTR (_PyObject_ManagedDictPointer (obj )-> dict ,
7166
- (PyDictObject * )Py_XNewRef (new_dict ));
7167
7247
}
7168
- Py_END_CRITICAL_SECTION2 ();
7169
-
7170
- if ( err == 0 ) {
7171
- Py_XDECREF ( dict ) ;
7248
+ if ( _PyDict_DetachFromObject ( dict , obj ) == 0 ) {
7249
+ _PyObject_ManagedDictPointer ( obj ) -> dict = ( PyDictObject * ) Py_XNewRef ( new_dict );
7250
+ Py_DECREF ( dict );
7251
+ return 0 ;
7172
7252
}
7253
+ return -1 ;
7254
+ #endif
7173
7255
}
7174
7256
else {
7175
7257
PyDictObject * dict ;
@@ -7182,7 +7264,13 @@ _PyObject_SetManagedDict(PyObject *obj, PyObject *new_dict)
7182
7264
(PyDictObject * )Py_XNewRef (new_dict ));
7183
7265
7184
7266
Py_END_CRITICAL_SECTION ();
7185
-
7267
+ #ifdef Py_GIL_DISABLED
7268
+ if (dict != NULL ) {
7269
+ Py_BEGIN_CRITICAL_SECTION (dict );
7270
+ SET_DICT_SHARED_INLINE ((PyObject * )dict );
7271
+ Py_END_CRITICAL_SECTION ();
7272
+ }
7273
+ #endif
7186
7274
Py_XDECREF (dict );
7187
7275
}
7188
7276
assert (_PyObject_InlineValuesConsistencyCheck (obj ));
0 commit comments