@@ -1187,6 +1187,41 @@ _PyDictKeys_StringLookup(PyDictKeysObject* dk, PyObject *key)
1187
1187
return unicodekeys_lookup_unicode (dk , key , hash );
1188
1188
}
1189
1189
1190
+
1191
+ #ifdef Py_GIL_DISABLED
1192
+
1193
+ // Version of _Py_dict_lookup specialized for when we have split keys and the
1194
+ // shared keys are locked.
1195
+ static Py_ssize_t
1196
+ splitdict_lookup_keys_lock_held (PyDictObject * mp , PyObject * key , Py_hash_t hash ,
1197
+ PyObject * * value_addr )
1198
+ {
1199
+ PyDictKeysObject * dk = mp -> ma_keys ;
1200
+ Py_ssize_t ix ;
1201
+
1202
+ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED (mp );
1203
+ ASSERT_KEYS_LOCKED (dk );
1204
+ assert (PyUnicode_CheckExact (key ));
1205
+ assert (dk -> dk_kind == DICT_KEYS_SPLIT );
1206
+
1207
+ ix = unicodekeys_lookup_unicode (dk , key , hash );
1208
+
1209
+ if (ix >= 0 ) {
1210
+ * value_addr = mp -> ma_values -> values [ix ];
1211
+ }
1212
+ else {
1213
+ * value_addr = NULL ;
1214
+ }
1215
+
1216
+ return ix ;
1217
+ }
1218
+
1219
+ static Py_ssize_t
1220
+ unicodekeys_lookup_unicode_threadsafe (PyDictKeysObject * dk , PyObject * key ,
1221
+ Py_hash_t hash );
1222
+
1223
+ #endif // Py_GIL_DISABLED
1224
+
1190
1225
/*
1191
1226
The basic lookup function used by all operations.
1192
1227
This is based on Algorithm D from Knuth Vol. 3, Sec. 6.4.
@@ -1216,9 +1251,23 @@ _Py_dict_lookup(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject **valu
1216
1251
1217
1252
if (kind != DICT_KEYS_GENERAL ) {
1218
1253
if (PyUnicode_CheckExact (key )) {
1219
- LOCK_KEYS_IF_SPLIT (dk , kind );
1254
+ #ifdef Py_GIL_DISABLED
1255
+ if (kind == DICT_KEYS_SPLIT ) {
1256
+ // A split dictionaries keys can be mutated by other
1257
+ // dictionaries but if we have a unicode key we can avoid
1258
+ // locking the shared keys.
1259
+ ix = unicodekeys_lookup_unicode_threadsafe (dk , key , hash );
1260
+ if (ix == DKIX_KEY_CHANGED ) {
1261
+ LOCK_KEYS (dk );
1262
+ ix = splitdict_lookup_keys_lock_held (mp , key , hash ,
1263
+ value_addr );
1264
+ UNLOCK_KEYS (dk );
1265
+ return ix ;
1266
+ }
1267
+ }
1268
+ #endif
1269
+
1220
1270
ix = unicodekeys_lookup_unicode (dk , key , hash );
1221
- UNLOCK_KEYS_IF_SPLIT (dk , kind );
1222
1271
}
1223
1272
else {
1224
1273
INCREF_KEYS_FT (dk );
@@ -1468,7 +1517,6 @@ _Py_dict_lookup_threadsafe(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyOb
1468
1517
Py_DECREF (value );
1469
1518
goto read_failed ;
1470
1519
}
1471
-
1472
1520
}
1473
1521
}
1474
1522
else {
@@ -1690,32 +1738,54 @@ insert_combined_dict(PyInterpreterState *interp, PyDictObject *mp,
1690
1738
return 0 ;
1691
1739
}
1692
1740
1693
- static int
1694
- split_dict_resize_and_insert (PyInterpreterState * interp , PyDictObject * mp ,
1695
- Py_hash_t hash , PyObject * key , PyObject * value )
1696
- {
1697
- /* Need to resize. */
1698
- int ins = insertion_resize (interp , mp , 1 );
1699
- if (ins < 0 ) {
1700
- return -1 ;
1701
- }
1702
- assert (!_PyDict_HasSplitTable (mp ));
1703
- return insert_combined_dict (interp , mp , hash , key , value );
1704
- }
1705
-
1706
- static int
1741
+ // Performs an insert into a split dictionary when the key is not already
1742
+ // present. Returns 0 on success, -1 on error, and 1 if a race occured and
1743
+ // the key got added by another thread. Consumes key and value references
1744
+ // if the key and value are successfully inserted.
1745
+ static inline int
1707
1746
insert_split_dict (PyInterpreterState * interp , PyDictObject * mp ,
1708
- Py_hash_t hash , PyObject * key , PyObject * value )
1747
+ Py_hash_t hash , PyObject * key , PyObject * value ,
1748
+ Py_ssize_t * ix , PyObject * * result )
1709
1749
{
1750
+ ASSERT_DICT_LOCKED (mp );
1751
+
1710
1752
PyDictKeysObject * keys = mp -> ma_keys ;
1753
+ LOCK_KEYS (keys );
1754
+
1755
+ #ifdef Py_GIL_DISABLED
1756
+ PyObject * existing_value ;
1757
+ // We could have raced between our lookup before and the insert,
1758
+ // so we need to lookup again with the keys locked
1759
+ * ix = splitdict_lookup_keys_lock_held (mp , key , hash ,
1760
+ & existing_value );
1761
+ if (* ix >= 0 ) {
1762
+ UNLOCK_KEYS (keys );
1763
+ * result = existing_value ;
1764
+ // There was a race with someone else inserting the key, the
1765
+ // caller needs to handle it as they may not want to replace
1766
+ // the existing value.
1767
+ return 1 ;
1768
+ }
1769
+ #endif
1770
+
1771
+ uint64_t new_version = _PyDict_NotifyEvent (
1772
+ interp , PyDict_EVENT_ADDED , mp , key , value );
1773
+ keys -> dk_version = 0 ;
1711
1774
if (keys -> dk_usable <= 0 ) {
1712
- /* Need to resize. */
1775
+ UNLOCK_KEYS ( keys );
1713
1776
int ins = insertion_resize (interp , mp , 1 );
1714
1777
if (ins < 0 ) {
1715
1778
return -1 ;
1716
1779
}
1717
1780
assert (!_PyDict_HasSplitTable (mp ));
1718
- return insert_combined_dict (interp , mp , hash , key , value );
1781
+ if (insert_combined_dict (interp , mp , hash , key , value ) < 0 ) {
1782
+ * result = NULL ;
1783
+ return -1 ;
1784
+ }
1785
+
1786
+ * result = value ;
1787
+ mp -> ma_version_tag = new_version ;
1788
+ return 0 ;
1719
1789
}
1720
1790
1721
1791
Py_ssize_t hashpos = find_empty_slot (keys , hash );
@@ -1732,34 +1802,13 @@ insert_split_dict(PyInterpreterState *interp, PyDictObject *mp,
1732
1802
1733
1803
split_keys_entry_added (keys );
1734
1804
assert (keys -> dk_usable >= 0 );
1735
- return 0 ;
1736
- }
1737
1805
1738
- #ifdef Py_GIL_DISABLED
1739
-
1740
- static inline Py_ssize_t
1741
- splitdict_lookup_threadsafe (PyDictObject * mp , PyDictKeysObject * dk ,
1742
- PyObject * key , Py_ssize_t hash ,
1743
- PyObject * * value )
1744
- {
1745
- ASSERT_DICT_LOCKED (mp );
1746
-
1747
- Py_ssize_t ix = unicodekeys_lookup_unicode_threadsafe (dk , key , hash );
1748
-
1749
- if (ix >= 0 ) {
1750
- * value = mp -> ma_values -> values [ix ];
1751
- }
1752
- else if (ix == DKIX_KEY_CHANGED ) {
1753
- ix = _Py_dict_lookup (mp , key , hash , value );
1754
- }
1755
- else {
1756
- * value = 0 ;
1757
- }
1758
- return ix ;
1806
+ UNLOCK_KEYS (keys );
1807
+ * result = value ;
1808
+ mp -> ma_version_tag = new_version ;
1809
+ return 0 ;
1759
1810
}
1760
1811
1761
- #endif
1762
-
1763
1812
/*
1764
1813
Internal routine to insert a new item into the table.
1765
1814
Used both by the internal resize routine and by the public insert routine.
@@ -1780,75 +1829,37 @@ insertdict(PyInterpreterState *interp, PyDictObject *mp,
1780
1829
assert (mp -> ma_keys -> dk_kind == DICT_KEYS_GENERAL );
1781
1830
}
1782
1831
1783
- Py_ssize_t ix ;
1784
- #ifdef Py_GIL_DISABLED
1785
- PyDictKeysObject * dk = mp -> ma_keys ;
1786
- DictKeysKind kind = dk -> dk_kind ;
1787
-
1788
- if (kind == DICT_KEYS_SPLIT ) {
1789
- // Split keys can be mutated by other dictionaries, so we need
1790
- // to do a threadsafe lookup here. This is basically the split-key
1791
- // half of _Py_dict_lookup_threadsafe and avoids locking the
1792
- // shared keys if we can
1793
- assert (PyUnicode_CheckExact (key ));
1794
- ix = splitdict_lookup_threadsafe (mp , dk , key , hash , & old_value );
1795
- } else {
1796
- ix = _Py_dict_lookup (mp , key , hash , & old_value );
1797
- }
1798
- #else
1799
- ix = _Py_dict_lookup (mp , key , hash , & old_value );
1800
- #endif
1832
+ Py_ssize_t ix = _Py_dict_lookup (mp , key , hash , & old_value );
1801
1833
if (ix == DKIX_ERROR )
1802
1834
goto Fail ;
1803
1835
1804
1836
MAINTAIN_TRACKING (mp , key , value );
1805
1837
1806
1838
if (ix == DKIX_EMPTY ) {
1807
- uint64_t new_version ;
1808
1839
if (!_PyDict_HasSplitTable (mp )) {
1809
- new_version = _PyDict_NotifyEvent (
1840
+ uint64_t new_version = _PyDict_NotifyEvent (
1810
1841
interp , PyDict_EVENT_ADDED , mp , key , value );
1811
1842
/* Insert into new slot. */
1812
1843
mp -> ma_keys -> dk_version = 0 ;
1813
1844
if (insert_combined_dict (interp , mp , hash , key , value ) < 0 ) {
1814
1845
goto Fail ;
1815
1846
}
1847
+ mp -> ma_version_tag = new_version ;
1816
1848
}
1817
1849
else {
1818
- LOCK_KEYS (mp -> ma_keys );
1819
-
1850
+ int res = insert_split_dict (interp , mp , hash , key ,
1851
+ value , & ix , & old_value );
1852
+ if (res < 0 ) {
1853
+ return -1 ;
1854
+ }
1820
1855
#ifdef Py_GIL_DISABLED
1821
- // We could have raced between our lookup before and the insert,
1822
- // so we need to lookup again with the keys locked
1823
- ix = splitdict_lookup_threadsafe (mp , dk , key , hash , & old_value );
1824
- if (ix >= 0 ) {
1825
- UNLOCK_KEYS (mp -> ma_keys );
1856
+ else if (res == 1 ) {
1826
1857
goto insert_on_split_race ;
1827
1858
}
1828
1859
#endif
1829
- new_version = _PyDict_NotifyEvent (
1830
- interp , PyDict_EVENT_ADDED , mp , key , value );
1831
- /* Insert into new slot. */
1832
- mp -> ma_keys -> dk_version = 0 ;
1833
- if (mp -> ma_keys -> dk_usable <= 0 ) {
1834
- UNLOCK_KEYS (mp -> ma_keys );
1835
-
1836
- if (split_dict_resize_and_insert (interp , mp , hash , key , value ) < 0 ) {
1837
- goto Fail ;
1838
- }
1839
- } else {
1840
- int insert = insert_split_dict (interp , mp , hash , key , value );
1841
- UNLOCK_KEYS (mp -> ma_keys );
1842
-
1843
- if (insert < 0 ) {
1844
- goto Fail ;
1845
- }
1846
- }
1847
- mp -> ma_keys -> dk_version = new_version ;
1848
1860
}
1849
1861
1850
1862
mp -> ma_used ++ ;
1851
- mp -> ma_version_tag = new_version ;
1852
1863
ASSERT_CONSISTENT (mp );
1853
1864
return 0 ;
1854
1865
}
@@ -4290,12 +4301,12 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu
4290
4301
}
4291
4302
4292
4303
if (ix == DKIX_EMPTY ) {
4293
- uint64_t new_version = _PyDict_NotifyEvent (
4294
- interp , PyDict_EVENT_ADDED , mp , key , default_value );
4295
- mp -> ma_keys -> dk_version = 0 ;
4296
4304
value = default_value ;
4297
4305
4298
4306
if (!_PyDict_HasSplitTable (mp )) {
4307
+ uint64_t new_version = _PyDict_NotifyEvent (
4308
+ interp , PyDict_EVENT_ADDED , mp , key , default_value );
4309
+ mp -> ma_keys -> dk_version = 0 ;
4299
4310
if (insert_combined_dict (interp , mp , hash , Py_NewRef (key ), Py_NewRef (value )) < 0 ) {
4300
4311
Py_DECREF (key );
4301
4312
Py_DECREF (value );
@@ -4304,60 +4315,44 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu
4304
4315
}
4305
4316
return -1 ;
4306
4317
}
4318
+ mp -> ma_version_tag = new_version ;
4307
4319
}
4308
4320
else {
4309
- LOCK_KEYS (mp -> ma_keys );
4310
- #ifdef Py_GIL_DISABLED
4311
- // We could have raced between our lookup before and the insert,
4312
- // so we need to lookup again with the keys locked
4313
- ix = _Py_dict_lookup (mp , key , hash , & value );
4314
- if (ix >= 0 ) {
4315
- UNLOCK_KEYS (mp -> ma_keys );
4316
- if (value != NULL ) {
4317
- if (result ) {
4318
- * result = incref_result ? Py_NewRef (value ) : value ;
4319
- }
4320
- return 0 ;
4321
- }
4322
- goto insert_on_split_race ;
4323
- }
4324
- #endif
4325
- if (mp -> ma_keys -> dk_usable <= 0 ) {
4326
- UNLOCK_KEYS (mp -> ma_keys );
4327
-
4328
- if (split_dict_resize_and_insert (interp , mp , hash , key , value ) < 0 ) {
4329
- return -1 ;
4330
- }
4331
- } else {
4332
- int insert = insert_split_dict (interp , mp , hash , Py_NewRef (key ), Py_NewRef (value ));
4333
- UNLOCK_KEYS (mp -> ma_keys );
4334
-
4335
- if (insert < 0 ) {
4336
- Py_DECREF (key );
4337
- Py_DECREF (value );
4321
+ int res = insert_split_dict (interp , mp , hash , Py_NewRef (key ),
4322
+ Py_NewRef (default_value ), & ix , & value );
4323
+ if (res ) {
4324
+ Py_DECREF (key );
4325
+ Py_DECREF (value );
4326
+ if (res < 0 ) {
4338
4327
if (result ) {
4339
4328
* result = NULL ;
4340
4329
}
4341
4330
return -1 ;
4342
4331
}
4332
+
4333
+ #ifdef Py_GIL_DISABLED
4334
+ // Raced with another thread inserting
4335
+ goto insert_on_split_race ;
4336
+ #endif
4343
4337
}
4344
4338
}
4345
4339
4346
4340
MAINTAIN_TRACKING (mp , key , value );
4347
4341
mp -> ma_used ++ ;
4348
- mp -> ma_version_tag = new_version ;
4349
4342
assert (mp -> ma_keys -> dk_usable >= 0 );
4350
4343
ASSERT_CONSISTENT (mp );
4351
4344
if (result ) {
4352
4345
* result = incref_result ? Py_NewRef (value ) : value ;
4353
4346
}
4354
4347
return 0 ;
4355
4348
}
4356
- else if ( value == NULL ) {
4349
+
4357
4350
#ifdef Py_GIL_DISABLED
4358
4351
insert_on_split_race :
4359
4352
#endif
4360
- uint64_t new_version = _PyDict_NotifyEvent (
4353
+ if (value == NULL ) {
4354
+ uint64_t new_version ;
4355
+ new_version = _PyDict_NotifyEvent (
4361
4356
interp , PyDict_EVENT_ADDED , mp , key , default_value );
4362
4357
value = default_value ;
4363
4358
assert (_PyDict_HasSplitTable (mp ));
0 commit comments