@@ -151,7 +151,7 @@ ASSERT_DICT_LOCKED(PyObject *op)
151
151
}
152
152
#define ASSERT_DICT_LOCKED (op ) ASSERT_DICT_LOCKED(_Py_CAST(PyObject*, op))
153
153
154
- #define LOCK_KEYS (keys ) PyMutex_Lock (&keys->dk_mutex)
154
+ #define LOCK_KEYS (keys ) PyMutex_LockFlags (&keys->dk_mutex, _Py_LOCK_DONT_DETACH )
155
155
#define UNLOCK_KEYS (keys ) PyMutex_Unlock(&keys->dk_mutex)
156
156
157
157
#define ASSERT_KEYS_LOCKED (keys ) assert(PyMutex_IsLocked(&keys->dk_mutex))
@@ -161,6 +161,15 @@ ASSERT_DICT_LOCKED(PyObject *op)
161
161
#define INCREF_KEYS (dk ) _Py_atomic_add_ssize(&dk->dk_refcnt, 1)
162
162
// Dec refs the keys object, giving the previous value
163
163
#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
+ }
164
173
165
174
#else /* Py_GIL_DISABLED */
166
175
@@ -172,6 +181,11 @@ ASSERT_DICT_LOCKED(PyObject *op)
172
181
#define STORE_SHARED_KEY (key , value ) key = value
173
182
#define INCREF_KEYS (dk ) dk->dk_refcnt++
174
183
#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
+ }
175
189
176
190
#endif
177
191
@@ -809,15 +823,25 @@ new_dict(PyInterpreterState *interp,
809
823
static inline size_t
810
824
shared_keys_usable_size (PyDictKeysObject * keys )
811
825
{
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
813
837
return (size_t )keys -> dk_nentries + (size_t )keys -> dk_usable ;
838
+ #endif
814
839
}
815
840
816
841
/* Consumes a reference to the keys object */
817
842
static PyObject *
818
843
new_dict_with_shared_keys (PyInterpreterState * interp , PyDictKeysObject * keys )
819
844
{
820
- LOCK_KEYS (keys );
821
845
size_t size = shared_keys_usable_size (keys );
822
846
PyDictValues * values = new_values (size );
823
847
if (values == NULL ) {
@@ -830,7 +854,6 @@ new_dict_with_shared_keys(PyInterpreterState *interp, PyDictKeysObject *keys)
830
854
values -> values [i ] = NULL ;
831
855
}
832
856
PyObject * res = new_dict (interp , keys , values , 0 , 1 );
833
- UNLOCK_KEYS (keys );
834
857
return res ;
835
858
}
836
859
@@ -1269,8 +1292,7 @@ insert_into_dictkeys(PyDictKeysObject *keys, PyObject *name)
1269
1292
dictkeys_set_index (keys , hashpos , ix );
1270
1293
assert (ep -> me_key == NULL );
1271
1294
ep -> me_key = Py_NewRef (name );
1272
- keys -> dk_usable -- ;
1273
- keys -> dk_nentries ++ ;
1295
+ split_keys_entry_added (keys );
1274
1296
}
1275
1297
assert (ix < SHARED_KEYS_MAX_SIZE );
1276
1298
return ix ;
@@ -1279,7 +1301,7 @@ insert_into_dictkeys(PyDictKeysObject *keys, PyObject *name)
1279
1301
1280
1302
static inline int
1281
1303
insert_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 )
1283
1305
{
1284
1306
if (mp -> ma_keys -> dk_usable <= 0 ) {
1285
1307
/* Need to resize. */
@@ -1340,8 +1362,7 @@ insert_split_dict(PyInterpreterState *interp, PyDictObject *mp,
1340
1362
assert (mp -> ma_values -> values [index ] == NULL );
1341
1363
mp -> ma_values -> values [index ] = value ;
1342
1364
1343
- keys -> dk_usable -- ;
1344
- keys -> dk_nentries ++ ;
1365
+ split_keys_entry_added (keys );
1345
1366
assert (keys -> dk_usable >= 0 );
1346
1367
UNLOCK_KEYS (keys );
1347
1368
return 0 ;
@@ -3471,9 +3492,7 @@ copy_lock_held(PyObject *o)
3471
3492
3472
3493
if (_PyDict_HasSplitTable (mp )) {
3473
3494
PyDictObject * split_copy ;
3474
- LOCK_KEYS (mp -> ma_keys );
3475
3495
size_t size = shared_keys_usable_size (mp -> ma_keys );
3476
- UNLOCK_KEYS (mp -> ma_keys );
3477
3496
PyDictValues * newvalues = new_values (size );
3478
3497
if (newvalues == NULL )
3479
3498
return PyErr_NoMemory ();
@@ -3608,7 +3627,7 @@ dict_equal_lock_held(PyDictObject *a, PyDictObject *b)
3608
3627
Py_hash_t hash ;
3609
3628
if (DK_IS_UNICODE (a -> ma_keys )) {
3610
3629
PyDictUnicodeEntry * ep = & DK_UNICODE_ENTRIES (a -> ma_keys )[i ];
3611
- key = LOAD_SHARED_KEY ( ep -> me_key ) ;
3630
+ key = ep -> me_key ;
3612
3631
if (key == NULL ) {
3613
3632
continue ;
3614
3633
}
@@ -3995,8 +4014,8 @@ dict_popitem_impl(PyDictObject *self)
3995
4014
LOCK_KEYS (keys );
3996
4015
3997
4016
int status = dictresize (interp , self , DK_LOG_SIZE (self -> ma_keys ), 1 );
3998
- dictkeys_decref (interp , keys );
3999
4017
UNLOCK_KEYS (keys );
4018
+ dictkeys_decref (interp , keys );
4000
4019
4001
4020
if (status < 0 ) {
4002
4021
Py_DECREF (res );
@@ -4103,9 +4122,7 @@ sizeof_lock_held(PyDictObject *mp)
4103
4122
{
4104
4123
size_t res = _PyObject_SIZE (Py_TYPE (mp ));
4105
4124
if (_PyDict_HasSplitTable (mp )) {
4106
- LOCK_KEYS (mp -> ma_keys );
4107
4125
res += shared_keys_usable_size (mp -> ma_keys ) * sizeof (PyObject * );
4108
- UNLOCK_KEYS (mp -> ma_keys );
4109
4126
}
4110
4127
/* If the dictionary is split, the keys portion is accounted-for
4111
4128
in the type object. */
@@ -6030,12 +6047,19 @@ _PyObject_InitInlineValues(PyObject *obj, PyTypeObject *tp)
6030
6047
assert (tp -> tp_flags & Py_TPFLAGS_MANAGED_DICT );
6031
6048
PyDictKeysObject * keys = CACHED_KEYS (tp );
6032
6049
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
6033
6058
if (keys -> dk_usable > 1 ) {
6034
6059
keys -> dk_usable -- ;
6035
6060
}
6036
- LOCK_KEYS ( keys );
6061
+ #endif
6037
6062
size_t size = shared_keys_usable_size (keys );
6038
- UNLOCK_KEYS (keys );
6039
6063
PyDictValues * values = new_values (size );
6040
6064
if (values == NULL ) {
6041
6065
PyErr_NoMemory ();
@@ -6085,9 +6109,7 @@ make_dict_from_instance_attributes(PyInterpreterState *interp,
6085
6109
dictkeys_incref (keys );
6086
6110
Py_ssize_t used = 0 ;
6087
6111
Py_ssize_t track = 0 ;
6088
- LOCK_KEYS (keys );
6089
6112
size_t size = shared_keys_usable_size (keys );
6090
- UNLOCK_KEYS (keys );
6091
6113
for (size_t i = 0 ; i < size ; i ++ ) {
6092
6114
PyObject * val = values -> values [i ];
6093
6115
if (val != NULL ) {
0 commit comments