@@ -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
}
F438
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,55 @@ 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
1775
/* Need to resize. */
1776
+ UNLOCK_KEYS (keys );
1713
1777
int ins = insertion_resize (interp , mp , 1 );
1714
1778
if (ins < 0 ) {
1715
1779
return -1 ;
1716
1780
}
1717
1781
assert (!_PyDict_HasSplitTable (mp ));
1718
- return insert_combined_dict (interp , mp , hash , key , value );
1782
+ if (insert_combined_dict (interp , mp , hash , key , value ) < 0 ) {
1783
+ * result = NULL ;
1784
+ return -1 ;
1785
+ }
1786
+
1787
+ * result = value ;
1788
+ mp -> ma_version_tag = new_version ;
1789
+ return 0 ;
1719
1790
}
1720
1791
1721
1792
Py_ssize_t hashpos = find_empty_slot (keys , hash );
@@ -1732,34 +1803,13 @@ insert_split_dict(PyInterpreterState *interp, PyDictObject *mp,
1732
1803
1733
1804
split_keys_entry_added (keys );
1734
1805
assert (keys -> dk_usable >= 0 );
1735
- return 0 ;
1736
- }
1737
1806
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 ;
1807
+ UNLOCK_KEYS (keys );
1808
+ * result = value ;
1809
+ mp -> ma_version_tag = new_version ;
1810
+ return 0 ;
1759
1811
}
1760
1812
1761
- #endif
1762
-
1763
1813
/*
1764
1814
Internal routine to insert a new item into the table.
1765
1815
Used both by the internal resize routine and by the public insert routine.
@@ -1780,75 +1830,37 @@ insertdict(PyInterpreterState *interp, PyDictObject *mp,
1780
1830
assert (mp -> ma_keys -> dk_kind == DICT_KEYS_GENERAL );
1781
1831
}
1782
1832
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
1833
+ Py_ssize_t ix = _Py_dict_lookup (mp , key , hash , & old_value );
1801
1834
if (ix == DKIX_ERROR )
1802
1835
goto Fail ;
1803
1836
1804
1837
MAINTAIN_TRACKING (mp , key , value );
1805
1838
1806
1839
if (ix == DKIX_EMPTY ) {
1807
- uint64_t new_version ;
1808
1840
if (!_PyDict_HasSplitTable (mp )) {
1809
- new_version = _PyDict_NotifyEvent (
1841
+ uint64_t new_version = _PyDict_NotifyEvent (
1810
1842
interp , PyDict_EVENT_ADDED , mp , key , value );
1811
1843
/* Insert into new slot. */
1812
1844
mp -> ma_keys -> dk_version = 0 ;
1813
1845
if (insert_combined_dict (interp , mp , hash , key , value ) < 0 ) {
1814
1846
goto Fail ;
1815
1847
}
1848
+ mp -> ma_version_tag = new_version ;
1816
1849
}
1817
1850
else {
1818
- LOCK_KEYS (mp -> ma_keys );
1819
-
1851
+ int res = insert_split_dict (interp , mp , hash , key ,
1852
+ value , & ix , & old_value );
1853
+ if (res < 0 ) {
1854
+ return -1 ;
1855
+ }
1820
1856
#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 );
1857
+ else if (res == 1 ) {
1826
1858
goto insert_on_split_race ;
1827
1859
}
1828
1860
#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
1861
}
1849
1862
1850
1863
mp -> ma_used ++ ;
1851
- mp -> ma_version_tag = new_version ;
1852
1864
ASSERT_CONSISTENT (mp );
1853
1865
return 0 ;
1854
1866
}
@@ -4290,12 +4302,12 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu
4290
4302
}
4291
4303
4292
4304
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
4305
value = default_value ;
4297
4306
4298
4307
if (!_PyDict_HasSplitTable (mp )) {
4308
+ uint64_t new_version = _PyDict_NotifyEvent (
4309
+ interp , PyDict_EVENT_ADDED , mp , key , default_value );
4310
+ mp -> ma_keys -> dk_version = 0 ;
4299
4311
if (insert_combined_dict (interp , mp , hash , Py_NewRef (key ), Py_NewRef (value )) < 0 ) {
4300
4312
Py_DECREF (key );
4301
4313
Py_DECREF (value );
@@ -4304,60 +4316,44 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu
4304
4316
}
4305
4317
return -1 ;
4306
4318
}
4319
+ mp -> ma_version_tag = new_version ;
4307
4320
}
4308
4321
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 );
4322
+ int res = insert_split_dict (interp , mp , hash , Py_NewRef (key ),
4323
+ Py_NewRef (default_value ), & ix , & value );
4324
+ if (res ) {
4325
+ Py_DECREF (key );
4326
+ Py_DECREF (value );
4327
+ if (res < 0 ) {
4338
4328
if (result ) {
4339
4329
* result = NULL ;
4340
4330
}
4341
4331
return -1 ;
4342
4332
}
4333
+
4334
+ #ifdef Py_GIL_DISABLED
4335
+ // Raced with another thread inserting
4336
+ goto insert_on_split_race ;
4337
+ #endif
4343
4338
}
4344
4339
}
4345
4340
4346
4341
MAINTAIN_TRACKING (mp , key , value );
4347
4342
mp -> ma_used ++ ;
4348
- mp -> ma_version_tag = new_version ;
4349
4343
assert (mp -> ma_keys -> dk_usable >= 0 );
4350
4344
ASSERT_CONSISTENT (mp );
4351
4345
if (result ) {
4352
4346
* result = incref_result ? Py_NewRef (value ) : value ;
4353
4347
}
4354
4348
return 0 ;
4355
4349
}
4356
- else if ( value == NULL ) {
4350
+
4357
4351
#ifdef Py_GIL_DISABLED
4358
4352
insert_on_split_race :
4359
4353
#endif
4360
- uint64_t new_version = _PyDict_NotifyEvent (
4354
+ if (value == NULL ) {
4355
+ uint64_t new_version ;
4356
+ new_version = _PyDict_NotifyEvent (
4361
4357
interp , PyDict_EVENT_ADDED , mp , key , default_value );
4362
4358
value = default_value ;
4363
4359
assert (_PyDict_HasSplitTable (mp ));
0 commit comments