@@ -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
@@ -3198,7 +3202,7 @@ dict_dealloc(PyObject *self)
31983202        assert (keys -> dk_refcnt  ==  1  ||  keys  ==  Py_EMPTY_KEYS );
31993203        dictkeys_decref (interp , keys , false);
32003204    }
3201-     if  (Py_IS_TYPE (mp , & PyDict_Type )) {
3205+     if  (Py_IS_TYPE (mp , & PyDict_Type )  &&  ! IS_DICT_SHARED_INLINE ( mp ) ) {
32023206        _Py_FREELIST_FREE (dicts , mp , Py_TYPE (mp )-> tp_free );
32033207    }
32043208    else  {
@@ -7126,6 +7130,75 @@ set_dict_inline_values(PyObject *obj, PyDictObject *new_dict)
71267130    }
71277131}
71287132
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+ 
71297202int 
71307203_PyObject_SetManagedDict (PyObject  * obj , PyObject  * new_dict )
71317204{
@@ -7134,42 +7207,51 @@ _PyObject_SetManagedDict(PyObject *obj, PyObject *new_dict)
71347207    int  err  =  0 ;
71357208    PyTypeObject  * tp  =  Py_TYPE (obj );
71367209    if  (tp -> tp_flags  &  Py_TPFLAGS_INLINE_VALUES ) {
7137-         PyDictObject  * dict  =  _PyObject_GetManagedDict (obj );
7138-         if  (dict  ==  NULL ) {
71397210#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 ;
71417218
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 );
71467221
7147-             Py_END_CRITICAL_SECTION ( );
7222+             err   =   replace_dict_probably_inline_materialized ( obj ,  prev_dict ,  new_dict ,  & replaced_dict );
71487223
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 ;
71517229            }
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 ;
71527242#else 
7243+         PyDictObject  * dict  =  _PyObject_GetManagedDict (obj );
7244+         if  (dict  ==  NULL ) {
71537245            set_dict_inline_values (obj , (PyDictObject  * )new_dict );
71547246            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 ));
71677247        }
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 ;
71727252        }
7253+         return  -1 ;
7254+ #endif 
71737255    }
71747256    else  {
71757257        PyDictObject  * dict ;
@@ -7182,7 +7264,13 @@ _PyObject_SetManagedDict(PyObject *obj, PyObject *new_dict)
71827264                            (PyDictObject  * )Py_XNewRef (new_dict ));
71837265
71847266        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 
71867274        Py_XDECREF (dict );
71877275    }
71887276    assert (_PyObject_InlineValuesConsistencyCheck (obj ));
0 commit comments