@@ -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
@@ -3117,7 +3121,7 @@ dict_dealloc(PyObject *self)
3117
3121
assert (keys -> dk_refcnt == 1 || keys == Py_EMPTY_KEYS );
3118
3122
dictkeys_decref (interp , keys , false);
3119
3123
}
3120
- if (Py_IS_TYPE (mp , & PyDict_Type )) {
3124
+ if (Py_IS_TYPE (mp , & PyDict_Type ) && ! IS_DICT_SHARED_INLINE ( mp ) ) {
3121
3125
_Py_FREELIST_FREE (dicts , mp , Py_TYPE (mp )-> tp_free );
3122
3126
}
3123
3127
else {
@@ -7030,6 +7034,75 @@ set_dict_inline_values(PyObject *obj, PyDictObject *new_dict)
7030
7034
}
7031
7035
}
7032
7036
7037
+ #ifdef Py_GIL_DISABLED
7038
+
7039
+ // Trys and sets the dictionary for an object in the easy case when our current
7040
+ // dictionary is either completely not materialized or is a dictionary which
7041
+ // does not point at the inline values.
7042
+ static bool
7043
+ try_set_dict_inline_only_or_other_dict (PyObject * obj , PyObject * new_dict , PyDictObject * * cur_dict )
7044
+ {
7045
+ bool replaced = false;
7046
+ Py_BEGIN_CRITICAL_SECTION (obj );
7047
+
7048
+ PyDictObject * dict = * cur_dict = _PyObject_GetManagedDict (obj );
7049
+ if (dict == NULL ) {
7050
+ // We only have inline values, we can just completely replace them.
7051
+ set_dict_inline_values (obj , (PyDictObject * )new_dict );
7052
+ replaced = true;
7053
+ goto exit_lock ;
7054
+ }
7055
+
7056
+ if (FT_ATOMIC_LOAD_PTR_RELAXED (dict -> ma_values ) != _PyObject_InlineValues (obj )) {
7057
+ // We have a materialized dict which doesn't point at the inline values,
7058
+ // We get to simply swap dictionaries and free the old dictionary.
7059
+ FT_ATOMIC_STORE_PTR (_PyObject_ManagedDictPointer (obj )-> dict ,
7060
+ (PyDictObject * )Py_XNewRef (new_dict ));
7061
+ replaced = true;
7062
+ goto exit_lock ;
7063
+ } else {
7064
+ // We have inline values, we need to lock the dict and the object
7065
+ // at the same time to safely dematerialize them. To do that while releasing
7066
+ // the object lock we need a strong reference to the current dictionary.
7067
+ Py_INCREF (dict );
7068
+ }
7069
+ exit_lock :
7070
+ Py_END_CRITICAL_SECTION ();
7071
+ return replaced ;
7072
+ }
7073
+
7074
+ #endif
7075
+
7076
+ // Replaces a dictionary that is probably the dictionary which has been
7077
+ // materialized and points at the inline values. We could have raced
7078
+ // and replaced it with another dictionary though.
7079
+ static int
7080
+ replace_dict_probably_inline_materialized (PyObject * obj , PyDictObject * inline_dict ,
7081
+ PyObject * new_dict , PyDictObject * * replaced_dict )
7082
+ {
7083
+ // But we could have had another thread race in after we released
7084
+ // the object lock
7085
+ int err = 0 ;
7086
+ * replaced_dict = _PyObject_GetManagedDict (obj );
7087
+ assert (FT_ATOMIC_LOAD_PTR_RELAXED (inline_dict -> ma_values ) == _PyObject_InlineValues (obj ));
7088
+
7089
+ if (* replaced_dict == inline_dict ) {
7090
+ err = _PyDict_DetachFromObject (inline_dict , obj );
7091
+ if (err != 0 ) {
7092
+ return err ;
7093
+ }
7094
+ SET_DICT_SHARED_INLINE ((PyObject * )inline_dict );
7095
+ // We incref'd the inline dict and the object owns a ref.
7096
+ // Clear the object's reference, we'll clear the local
7097
+ // reference after releasing the lock.
7098
+ Py_CLEAR (* replaced_dict );
7099
+ }
7100
+
7101
+ FT_ATOMIC_STORE_PTR (_PyObject_ManagedDictPointer (obj )-> dict ,
7102
+ (PyDictObject * )Py_XNewRef (new_dict ));
7103
+ return err ;
7104
+ }
7105
+
7033
7106
int
7034
7107
_PyObject_SetManagedDict (PyObject * obj , PyObject * new_dict )
7035
7108
{
@@ -7038,42 +7111,51 @@ _PyObject_SetManagedDict(PyObject *obj, PyObject *new_dict)
7038
7111
int err = 0 ;
7039
7112
PyTypeObject * tp = Py_TYPE (obj );
7040
7113
if (tp-> tp_flags & Py_TPFLAGS_INLINE_VALUES ) {
7041
- PyDictObject * dict = _PyObject_GetManagedDict (obj );
7042
- if (dict == NULL ) {
7043
7114
#ifdef Py_GIL_DISABLED
7044
- Py_BEGIN_CRITICAL_SECTION (obj );
7115
+ PyDictObject * prev_dict ;
7116
+ if (!try_set_dict_inline_only_or_other_dict (obj , new_dict , & prev_dict )) {
7117
+ // We had a materialized dictionary which pointed at the inline
7118
+ // values. We need to lock both the object and the dict at the
7119
+ // same time to safely replace it. We can't merely lock the dictionary
7120
+ // while the object is locked because it could suspend the object lock.
7121
+ PyDictObject * replaced_dict ;
7045
7122
7046
- dict = _PyObject_ManagedDictPointer (obj )-> dict ;
7047
- if (dict == NULL ) {
7048
- set_dict_inline_values (obj , (PyDictObject * )new_dict );
7049
- }
7123
+ assert (prev_dict != NULL );
7124
+ Py_BEGIN_CRITICAL_SECTION2 (obj , prev_dict );
7050
7125
7051
- Py_END_CRITICAL_SECTION ( );
7126
+ err = replace_dict_probably_inline_materialized ( obj , prev_dict , new_dict , & replaced_dict );
7052
7127
7053
- if (dict == NULL ) {
7054
- return 0 ;
7128
+ Py_END_CRITICAL_SECTION2 ();
7129
+
7130
+ Py_DECREF (prev_dict );
7131
+ if (err != 0 ) {
7132
+ return err ;
7055
7133
}
7134
+ prev_dict = replaced_dict ;
7135
+ }
7136
+
7137
+ if (prev_dict != NULL ) {
7138
+ Py_BEGIN_CRITICAL_SECTION (prev_dict );
7139
+ SET_DICT_SHARED_INLINE ((PyObject * )prev_dict );
7140
+ Py_END_CRITICAL_SECTION ();
7141
+ // Readers from the old dictionary use a borrowed reference. We need
7142
+ // to set the dict to be freed via QSBR which requires locking it.
7143
+ Py_DECREF (prev_dict );
7144
+ }
7145
+ return 0 ;
7056
7146
#else
7147
+ PyDictObject * dict = _PyObject_GetManagedDict (obj );
7148
+ if (dict == NULL ) {
7057
7149
set_dict_inline_values (obj , (PyDictObject * )new_dict );
7058
7150
return 0 ;
7059
- #endif
7060
- }
7061
-
7062
- Py_BEGIN_CRITICAL_SECTION2 (dict , obj );
7063
-
7064
- // We've locked dict, but the actual dict could have changed
7065
- // since we locked it.
7066
- dict = _PyObject_ManagedDictPointer (obj )-> dict ;
7067
- err = _PyDict_DetachFromObject (dict , obj );
7068
- if (err == 0 ) {
7069
- FT_ATOMIC_STORE_PTR (_PyObject_ManagedDictPointer (obj )-> dict ,
7070
- (PyDictObject * )Py_XNewRef (new_dict ));
7071
7151
}
7072
- Py_END_CRITICAL_SECTION2 ();
7073
-
7074
- if ( err == 0 ) {
7075
- Py_XDECREF ( dict ) ;
7152
+ if ( _PyDict_DetachFromObject ( dict , obj ) == 0 ) {
7153
+ * _PyObject_ManagedDictPointer ( obj ) -> dict = ( PyDictObject * ) Py_XNewRef ( new_dict );
7154
+ Py_DECREF ( dict );
7155
+ return 0 ;
7076
7156
}
7157
+ return -1 ;
7158
+ #endif
7077
7159
}
7078
7160
else {
7079
7161
PyDictObject * dict ;
@@ -7086,7 +7168,13 @@ _PyObject_SetManagedDict(PyObject *obj, PyObject *new_dict)
7086
7168
(PyDictObject * )Py_XNewRef (new_dict ));
7087
7169
7088
7170
Py_END_CRITICAL_SECTION ();
7089
-
7171
+ #ifdef Py_GIL_DISABLED
7172
+ if (dict != NULL ) {
7173
+ Py_BEGIN_CRITICAL_SECTION (dict );
7174
+ SET_DICT_SHARED_INLINE ((PyObject * )dict );
7175
+ Py_END_CRITICAL_SECTION ();
7176
+ }
7177
+ #endif
7090
7178
Py_XDECREF (dict );
7091
7179
}
7092
7180
assert (_PyObject_InlineValuesConsistencyCheck (obj ));
0 commit comments