@@ -165,6 +165,8 @@ ASSERT_DICT_LOCKED(PyObject *op)
165165
166166#define IS_DICT_SHARED (mp ) _PyObject_GC_IS_SHARED(mp)
167167#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)
168170#define LOAD_INDEX (keys ,size ,idx ) _Py_atomic_load_int##size##_relaxed(&((const int##size##_t*)keys->dk_indices)[idx]);
169171#define STORE_INDEX (keys ,size ,idx ,value ) _Py_atomic_store_int##size##_relaxed(&((int##size##_t*)keys->dk_indices)[idx], (int##size##_t)value);
170172#define ASSERT_OWNED_OR_SHARED (mp ) \
@@ -245,6 +247,8 @@ static inline void split_keys_entry_added(PyDictKeysObject *keys)
245247#define UNLOCK_KEYS_IF_SPLIT (keys ,kind )
246248#define IS_DICT_SHARED (mp ) (false)
247249#define SET_DICT_SHARED (mp )
250+ #define IS_DICT_SHARED_INLINE (mp ) (false)
251+ #define SET_DICT_SHARED_INLINE (mp )
248252#define LOAD_INDEX (keys ,size ,idx ) ((const int##size##_t*)(keys->dk_indices))[idx]
249253#define STORE_INDEX (keys ,size ,idx ,value ) ((int##size##_t*)(keys->dk_indices))[idx] = (int##size##_t)value
250254
@@ -3117,7 +3121,7 @@ dict_dealloc(PyObject *self)
31173121assert (keys -> dk_refcnt == 1 || keys == Py_EMPTY_KEYS );
31183122dictkeys_decref (interp ,keys , false);
31193123 }
3120- if (Py_IS_TYPE (mp ,& PyDict_Type )) {
3124+ if (Py_IS_TYPE (mp ,& PyDict_Type )&& ! IS_DICT_SHARED_INLINE ( mp ) ) {
31213125_Py_FREELIST_FREE (dicts ,mp ,Py_TYPE (mp )-> tp_free );
31223126 }
31233127else {
@@ -7030,6 +7034,75 @@ set_dict_inline_values(PyObject *obj, PyDictObject *new_dict)
70307034 }
70317035}
70327036
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+ gotoexit_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+ gotoexit_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+
70337106int
70347107_PyObject_SetManagedDict (PyObject * obj ,PyObject * new_dict )
70357108{
@@ -7038,42 +7111,51 @@ _PyObject_SetManagedDict(PyObject *obj, PyObject *new_dict)
70387111int err = 0 ;
70397112PyTypeObject * tp = Py_TYPE (obj );
70407113if (tp -> tp_flags & Py_TPFLAGS_INLINE_VALUES ) {
7041- PyDictObject * dict = _PyObject_GetManagedDict (obj );
7042- if (dict == NULL ) {
70437114#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 ;
70457122
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 );
70507125
7051- Py_END_CRITICAL_SECTION ( );
7126+ err = replace_dict_probably_inline_materialized ( obj , prev_dict , new_dict , & replaced_dict );
70527127
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 ;
70557133 }
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 ;
70567146#else
7147+ PyDictObject * dict = _PyObject_GetManagedDict (obj );
7148+ if (dict == NULL ) {
70577149set_dict_inline_values (obj , (PyDictObject * )new_dict );
70587150return 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 ));
70717151 }
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 ;
70767156 }
7157+ return -1 ;
7158+ #endif
70777159 }
70787160else {
70797161PyDictObject * dict ;
@@ -7086,7 +7168,13 @@ _PyObject_SetManagedDict(PyObject *obj, PyObject *new_dict)
70867168 (PyDictObject * )Py_XNewRef (new_dict ));
70877169
70887170Py_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
70907178Py_XDECREF (dict );
70917179 }
70927180assert (_PyObject_InlineValuesConsistencyCheck (obj ));