@@ -151,7 +151,7 @@ ASSERT_DICT_LOCKED(PyObject *op)
151151}
152152#define ASSERT_DICT_LOCKED (op ) ASSERT_DICT_LOCKED(_Py_CAST(PyObject*, op))
153153
154- #define LOCK_KEYS (keys ) PyMutex_Lock (&keys->dk_mutex)
154+ #define LOCK_KEYS (keys ) PyMutex_LockFlags (&keys->dk_mutex, _Py_LOCK_DONT_DETACH )
155155#define UNLOCK_KEYS (keys ) PyMutex_Unlock(&keys->dk_mutex)
156156
157157#define ASSERT_KEYS_LOCKED (keys ) assert(PyMutex_IsLocked(&keys->dk_mutex))
@@ -161,6 +161,15 @@ ASSERT_DICT_LOCKED(PyObject *op)
161161#define INCREF_KEYS (dk ) _Py_atomic_add_ssize(&dk->dk_refcnt, 1)
162162// Dec refs the keys object, giving the previous value
163163#define DECREF_KEYS (dk ) _Py_atomic_add_ssize(&dk->dk_refcnt, -1)
164+ static inline void split_keys_entry_added (PyDictKeysObject * keys )
165+ {
166+ ASSERT_KEYS_LOCKED (keys );
167+
168+ // We increase before we decrease so we never get too small of a value
169+ // when we're racing with reads
170+ _Py_atomic_store_ssize (& keys -> dk_nentries , keys -> dk_nentries + 1 );
171+ _Py_atomic_store_ssize (& keys -> dk_usable , keys -> dk_usable - 1 );
172+ }
164173
165174#else /* Py_GIL_DISABLED */
166175
@@ -172,6 +181,11 @@ ASSERT_DICT_LOCKED(PyObject *op)
172181#define STORE_SHARED_KEY (key , value ) key = value
173182#define INCREF_KEYS (dk ) dk->dk_refcnt++
174183#define DECREF_KEYS (dk ) dk->dk_refcnt--
184+ static inline void split_keys_entry_added (PyDictKeysObject * keys )
185+ {
186+ keys -> dk_usable -- ;
187+ keys -> dk_nentries ++ ;
188+ }
175189
176190#endif
177191
@@ -809,15 +823,25 @@ new_dict(PyInterpreterState *interp,
809823static inline size_t
810824shared_keys_usable_size (PyDictKeysObject * keys )
811825{
812- ASSERT_KEYS_LOCKED (keys );
826+ #ifdef Py_GIL_DISABLED
827+ // dk_usable will decrease for each instance that is created and each
828+ // value that is added. dk_entries will increase for each value that
829+ // is added. We want to always return the right value or larger.
830+ // We therefore increase dk_entries first and we decrease dk_usable
831+ // second, and conversely here we read dk_usable first and dk_entries
832+ // second (to avoid the case where we read entries before the increment
833+ // and read usable after the decrement)
834+ return (size_t )(_Py_atomic_load_ssize_acquire (& keys -> dk_usable ) +
835+ _Py_atomic_load_ssize_acquire (& keys -> dk_nentries ));
836+ #else
813837 return (size_t )keys -> dk_nentries + (size_t )keys -> dk_usable ;
838+ #endif
814839}
815840
816841/* Consumes a reference to the keys object */
817842static PyObject *
818843new_dict_with_shared_keys (PyInterpreterState * interp , PyDictKeysObject * keys )
819844{
820- LOCK_KEYS (keys );
821845 size_t size = shared_keys_usable_size (keys );
822846 PyDictValues * values = new_values (size );
823847 if (values == NULL ) {
@@ -830,7 +854,6 @@ new_dict_with_shared_keys(PyInterpreterState *interp, PyDictKeysObject *keys)
830854 values -> values [i ] = NULL ;
831855 }
832856 PyObject * res = new_dict (interp , keys , values , 0 , 1 );
833- UNLOCK_KEYS (keys );
834857 return res ;
835858}
836859
@@ -1269,8 +1292,7 @@ insert_into_dictkeys(PyDictKeysObject *keys, PyObject *name)
12691292 dictkeys_set_index (keys , hashpos , ix );
12701293 assert (ep -> me_key == NULL );
12711294 ep -> me_key = Py_NewRef (name );
1272- keys -> dk_usable -- ;
1273- keys -> dk_nentries ++ ;
1295+ split_keys_entry_added (keys );
12741296 }
12751297 assert (ix < SHARED_KEYS_MAX_SIZE );
12761298 return ix ;
@@ -1279,7 +1301,7 @@ insert_into_dictkeys(PyDictKeysObject *keys, PyObject *name)
12791301
12801302static inline int
12811303insert_combined_dict (PyInterpreterState * interp , PyDictObject * mp ,
1282- Py_hash_t hash , PyObject * key , PyObject * value , int unicode )
1304+ Py_hash_t hash , PyObject * key , PyObject * value , int unicode )
12831305{
12841306 if (mp -> ma_keys -> dk_usable <= 0 ) {
12851307 /* Need to resize. */
@@ -1340,8 +1362,7 @@ insert_split_dict(PyInterpreterState *interp, PyDictObject *mp,
13401362 assert (mp -> ma_values -> values [index ] == NULL );
13411363 mp -> ma_values -> values [index ] = value ;
13421364
1343- keys -> dk_usable -- ;
1344- keys -> dk_nentries ++ ;
1365+ split_keys_entry_added (keys );
13451366 assert (keys -> dk_usable >= 0 );
13461367 UNLOCK_KEYS (keys );
13471368 return 0 ;
@@ -3471,9 +3492,7 @@ copy_lock_held(PyObject *o)
34713492
34723493 if (_PyDict_HasSplitTable (mp )) {
34733494 PyDictObject * split_copy ;
3474- LOCK_KEYS (mp -> ma_keys );
34753495 size_t size = shared_keys_usable_size (mp -> ma_keys );
3476- UNLOCK_KEYS (mp -> ma_keys );
34773496 PyDictValues * newvalues = new_values (size );
34783497 if (newvalues == NULL )
34793498 return PyErr_NoMemory ();
@@ -3608,7 +3627,7 @@ dict_equal_lock_held(PyDictObject *a, PyDictObject *b)
36083627 Py_hash_t hash ;
36093628 if (DK_IS_UNICODE (a -> ma_keys )) {
36103629 PyDictUnicodeEntry * ep = & DK_UNICODE_ENTRIES (a -> ma_keys )[i ];
3611- key = LOAD_SHARED_KEY ( ep -> me_key ) ;
3630+ key = ep -> me_key ;
36123631 if (key == NULL ) {
36133632 continue ;
36143633 }
@@ -3995,8 +4014,8 @@ dict_popitem_impl(PyDictObject *self)
39954014 LOCK_KEYS (keys );
39964015
39974016 int status = dictresize (interp , self , DK_LOG_SIZE (self -> ma_keys ), 1 );
3998- dictkeys_decref (interp , keys );
39994017 UNLOCK_KEYS (keys );
4018+ dictkeys_decref (interp , keys );
40004019
40014020 if (status < 0 ) {
40024021 Py_DECREF (res );
@@ -4103,9 +4122,7 @@ sizeof_lock_held(PyDictObject *mp)
41034122{
41044123 size_t res = _PyObject_SIZE (Py_TYPE (mp ));
41054124 if (_PyDict_HasSplitTable (mp )) {
4106- LOCK_KEYS (mp -> ma_keys );
41074125 res += shared_keys_usable_size (mp -> ma_keys ) * sizeof (PyObject * );
4108- UNLOCK_KEYS (mp -> ma_keys );
41094126 }
41104127 /* If the dictionary is split, the keys portion is accounted-for
41114128 in the type object. */
@@ -6030,12 +6047,19 @@ _PyObject_InitInlineValues(PyObject *obj, PyTypeObject *tp)
60306047 assert (tp -> tp_flags & Py_TPFLAGS_MANAGED_DICT );
60316048 PyDictKeysObject * keys = CACHED_KEYS (tp );
60326049 assert (keys != NULL );
6050+ #ifdef Py_GIL_DISABLED
6051+ Py_ssize_t usable = keys -> dk_usable ;
6052+ while (usable > 1 ) {
6053+ if (_Py_atomic_compare_exchange_ssize (& keys -> dk_usable , & usable , usable - 1 )) {
6054+ break ;
6055+ }
6056+ }
6057+ #else
60336058 if (keys -> dk_usable > 1 ) {
60346059 keys -> dk_usable -- ;
60356060 }
6036- LOCK_KEYS ( keys );
6061+ #endif
60376062 size_t size = shared_keys_usable_size (keys );
6038- UNLOCK_KEYS (keys );
60396063 PyDictValues * values = new_values (size );
60406064 if (values == NULL ) {
60416065 PyErr_NoMemory ();
@@ -6085,9 +6109,7 @@ make_dict_from_instance_attributes(PyInterpreterState *interp,
60856109 dictkeys_incref (keys );
60866110 Py_ssize_t used = 0 ;
60876111 Py_ssize_t track = 0 ;
6088- LOCK_KEYS (keys );
60896112 size_t size = shared_keys_usable_size (keys );
6090- UNLOCK_KEYS (keys );
60916113 for (size_t i = 0 ; i < size ; i ++ ) {
60926114 PyObject * val = values -> values [i ];
60936115 if (val != NULL ) {
0 commit comments