@@ -143,7 +143,7 @@ it's USABLE_FRACTION (currently two-thirds) full.
143
143
144
144
#ifdef Py_GIL_DISABLED
145
145
146
- #define LOCK_KEYS (keys ) PyMutex_Lock (&keys->dk_mutex)
146
+ #define LOCK_KEYS (keys ) PyMutex_LockFlags (&keys->dk_mutex, _Py_LOCK_DONT_DETACH )
147
147
#define UNLOCK_KEYS (keys ) PyMutex_Unlock(&keys->dk_mutex)
148
148
149
149
#define ASSERT_KEYS_LOCKED (keys ) assert(PyMutex_IsLocked(&keys->dk_mutex))
@@ -153,6 +153,15 @@ it's USABLE_FRACTION (currently two-thirds) full.
153
153
#define INCREF_KEYS (dk ) _Py_atomic_add_ssize(&dk->dk_refcnt, 1)
154
154
// Dec refs the keys object, giving the previous value
155
155
#define DECREF_KEYS (dk ) _Py_atomic_add_ssize(&dk->dk_refcnt, -1)
156
+ static inline void split_keys_entry_added (PyDictKeysObject * keys )
157
+ {
158
+ ASSERT_KEYS_LOCKED (keys );
159
+
160
+ // We increase before we decrease so we never get too small of a value
161
+ // when we're racing with reads
162
+ _Py_atomic_store_ssize (& keys -> dk_nentries , keys -> dk_nentries + 1 );
163
+ _Py_atomic_store_ssize (& keys -> dk_usable , keys -> dk_usable - 1 );
164
+ }
156
165
157
166
#else
158
167
@@ -163,6 +172,11 @@ it's USABLE_FRACTION (currently two-thirds) full.
163
172
#define STORE_SHARED_KEY (key , value ) key = value
164
173
#define INCREF_KEYS (dk ) dk->dk_refcnt++
165
174
#define DECREF_KEYS (dk ) dk->dk_refcnt--
175
+ static inline void split_keys_entry_added (PyDictKeysObject * keys )
176
+ {
177
+ keys -> dk_usable -- ;
178
+ keys -> dk_nentries ++ ;
179
+ }
166
180
167
181
#endif
168
182
@@ -790,15 +804,25 @@ new_dict(PyInterpreterState *interp,
790
804
static inline size_t
791
805
shared_keys_usable_size (PyDictKeysObject * keys )
792
806
{
793
- ASSERT_KEYS_LOCKED (keys );
807
+ #ifdef Py_GIL_DISABLED
808
+ // dk_usable will decrease for each instance that is created and each
809
+ // value that is added. dk_entries will increase for each value that
810
+ // is added. We want to always return the right value or larger.
811
+ // We therefore increase dk_entries first and we decrease dk_usable
812
+ // second, and conversely here we read dk_usable first and dk_entries
813
+ // second (to avoid the case where we read entries before the increment
814
+ // and read usable after the decrement)
815
+ return (size_t )(_Py_atomic_load_ssize_acquire (& keys -> dk_usable ) +
816
+ _Py_atomic_load_ssize_acquire (& keys -> dk_nentries ));
817
+ #else
794
818
return (size_t )keys -> dk_nentries + (size_t )keys -> dk_usable ;
819
+ #endif
795
820
}
796
821
797
822
/* Consumes a reference to the keys object */
798
823
static PyObject *
799
824
new_dict_with_shared_keys (PyInterpreterState * interp , PyDictKeysObject * keys )
800
825
{
801
- LOCK_KEYS (keys );
802
826
size_t size = shared_keys_usable_size (keys );
803
827
PyDictValues * values = new_values (size );
804
828
if (values == NULL ) {
@@ -811,7 +835,6 @@ new_dict_with_shared_keys(PyInterpreterState *interp, PyDictKeysObject *keys)
811
835
values -> values [i ] = NULL ;
812
836
}
813
837
PyObject * res = new_dict (interp , keys , values , 0 , 1 );
814
- UNLOCK_KEYS (keys );
815
838
return res ;
816
839
}
817
840
@@ -1248,8 +1271,7 @@ insert_into_dictkeys(PyDictKeysObject *keys, PyObject *name)
1248
1271
dictkeys_set_index (keys , hashpos , ix );
1249
1272
assert (ep -> me_key == NULL );
1250
1273
ep -> me_key = Py_NewRef (name );
1251
- keys -> dk_usable -- ;
1252
- keys -> dk_nentries ++ ;
1274
+ split_keys_entry_added (keys );
1253
1275
}
1254
1276
assert (ix < SHARED_KEYS_MAX_SIZE );
1255
1277
return ix ;
@@ -1258,7 +1280,7 @@ insert_into_dictkeys(PyDictKeysObject *keys, PyObject *name)
1258
1280
1259
1281
static inline int
1260
1282
insert_combined_dict (PyInterpreterState * interp , PyDictObject * mp ,
1261
- Py_hash_t hash , PyObject * key , PyObject * value , int unicode )
1283
+ Py_hash_t hash , PyObject * key , PyObject * value , int unicode )
1262
1284
{
1263
1285
if (mp -> ma_keys -> dk_usable <= 0 ) {
1264
1286
/* Need to resize. */
@@ -1319,8 +1341,7 @@ insert_split_dict(PyInterpreterState *interp, PyDictObject *mp,
1319
1341
assert (mp -> ma_values -> values [index ] == NULL );
1320
1342
mp -> ma_values -> values [index ] = value ;
1321
1343
1322
- keys -> dk_usable -- ;
1323
- keys -> dk_nentries ++ ;
1344
+ split_keys_entry_added (keys );
1324
1345
assert (keys -> dk_usable >= 0 );
1325
1346
UNLOCK_KEYS (keys );
1326
1347
return 0 ;
@@ -3234,9 +3255,7 @@ PyDict_Copy(PyObject *o)
3234
3255
3235
3256
if (_PyDict_HasSplitTable (mp )) {
3236
3257
PyDictObject * split_copy ;
3237
- LOCK_KEYS (mp -> ma_keys );
3238
3258
size_t size = shared_keys_usable_size (mp -> ma_keys );
3239
- UNLOCK_KEYS (mp -> ma_keys );
3240
3259
PyDictValues * newvalues = new_values (size );
3241
3260
if (newvalues == NULL )
3242
3261
return PyErr_NoMemory ();
@@ -3351,7 +3370,7 @@ dict_equal(PyDictObject *a, PyDictObject *b)
3351
3370
Py_hash_t hash ;
3352
3371
if (DK_IS_UNICODE (a -> ma_keys )) {
3353
3372
PyDictUnicodeEntry * ep = & DK_UNICODE_ENTRIES (a -> ma_keys )[i ];
3354
- key = LOAD_SHARED_KEY ( ep -> me_key ) ;
3373
+ key = ep -> me_key ;
3355
3374
if (key == NULL ) {
3356
3375
continue ;
3357
3376
}
@@ -3661,8 +3680,8 @@ dict_popitem_impl(PyDictObject *self)
3661
3680
LOCK_KEYS (keys );
3662
3681
3663
3682
int status = dictresize (interp , self , DK_LOG_SIZE (self -> ma_keys ), 1 );
3664
- dictkeys_decref (interp , keys );
3665
3683
UNLOCK_KEYS (keys );
3684
+ dictkeys_decref (interp , keys );
3666
3685
3667
3686
if (status < 0 ) {
3668
3687
Py_DECREF (res );
@@ -3769,9 +3788,7 @@ _PyDict_SizeOf(PyDictObject *mp)
3769
3788
{
3770
3789
size_t res = _PyObject_SIZE (Py_TYPE (mp ));
3771
3790
if (_PyDict_HasSplitTable (mp )) {
3772
- LOCK_KEYS (mp -> ma_keys );
3773
3791
res += shared_keys_usable_size (mp -> ma_keys ) * sizeof (PyObject * );
3774
- UNLOCK_KEYS (mp -> ma_keys );
3775
3792
}
3776
3793
/* If the dictionary is split, the keys portion is accounted-for
3777
3794
in the type object. */
@@ -5589,12 +5606,19 @@ _PyObject_InitInlineValues(PyObject *obj, PyTypeObject *tp)
5589
5606
assert (tp -> tp_flags & Py_TPFLAGS_MANAGED_DICT );
5590
5607
PyDictKeysObject * keys = CACHED_KEYS (tp );
5591
5608
assert (keys != NULL );
5609
+ #ifdef Py_GIL_DISABLED
5610
+ Py_ssize_t usable = keys -> dk_usable ;
5611
+ while (usable > 1 ) {
5612
+ if (_Py_atomic_compare_exchange_ssize (& keys -> dk_usable , & usable , usable - 1 )) {
5613
+ break ;
5614
+ }
5615
+ }
5616
+ #else
5592
5617
if (keys -> dk_usable > 1 ) {
5593
5618
keys -> dk_usable -- ;
5594
5619
}
5595
- LOCK_KEYS ( keys );
5620
+ #endif
5596
5621
size_t size = shared_keys_usable_size (keys );
5597
- UNLOCK_KEYS (keys );
5598
5622
PyDictValues * values = new_values (size );
5599
5623
if (values == NULL ) {
5600
5624
PyErr_NoMemory ();
@@ -5644,9 +5668,7 @@ make_dict_from_instance_attributes(PyInterpreterState *interp,
5644
5668
dictkeys_incref (keys );
5645
5669
Py_ssize_t used = 0 ;
5646
5670
Py_ssize_t track = 0 ;
5647
- LOCK_KEYS (keys );
5648
5671
size_t size = shared_keys_usable_size (keys );
5649
- UNLOCK_KEYS (keys );
5650
5672
for (size_t i = 0 ; i < size ; i ++ ) {
5651
5673
PyObject * val = values -> values [i ];
5652
5674
if (val != NULL ) {
0 commit comments