10000 Re-probe for index with shared keys locked before adding split index · python/cpython@90fdea3 · GitHub
[go: up one dir, main page]

Skip to content

Commit 90fdea3

Browse files
committed
Re-probe for index with shared keys locked before adding split index
1 parent 5f498f7 commit 90fdea3

File tree

1 file changed

+113
-28
lines changed

1 file changed

+113
-28
lines changed

Objects/dictobject.c

Lines changed: 113 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1691,15 +1691,26 @@ insert_combined_dict(PyInterpreterState *interp, PyDictObject *mp,
16911691
return 0;
16921692
}
16931693

1694+
static int
1695+
split_dict_resize_and_insert(PyInterpreterState *interp, PyDictObject *mp,
1696+
Py_hash_t hash, PyObject *key, PyObject *value)
1697+
{
1698+
/* Need to resize. */
1699+
int ins = insertion_resize(interp, mp, 1);
1700+
if (ins < 0) {
1701+
return -1;
1702+
}
1703+
assert(!_PyDict_HasSplitTable(mp));
1704+
return insert_combined_dict(interp, mp, hash, key, value);
1705+
}
1706+
16941707
static int
16951708
insert_split_dict(PyInterpreterState *interp, PyDictObject *mp,
16961709
Py_hash_t hash, PyObject *key, PyObject *value)
16971710
{
16981711
PyDictKeysObject *keys = mp->ma_keys;
1699-
LOCK_KEYS(keys);
17001712
if (keys->dk_usable <= 0) {
17011713
/* Need to resize. */
1702-
UNLOCK_KEYS(keys);
17031714
int ins = insertion_resize(interp, mp, 1);
17041715
if (ins < 0) {
17051716
return -1;
@@ -1722,10 +1733,34 @@ insert_split_dict(PyInterpreterState *interp, PyDictObject *mp,
17221733

17231734
split_keys_entry_added(keys);
17241735
assert(keys->dk_usable >= 0);
1725-
UNLOCK_KEYS(keys);
17261736
return 0;
17271737
}
17281738

1739+
#ifdef Py_GIL_DISABLED
1740+
1741+
static inline Py_ssize_t
1742+
splitdict_lookup_threadsafe(PyDictObject *mp, PyDictKeysObject *dk,
1743+
PyObject *key, Py_ssize_t hash,
1744+
PyObject **value)
1745+
{
1746+
ASSERT_DICT_LOCKED(mp);
1747+
1748+
Py_ssize_t ix = unicodekeys_lookup_unicode_threadsafe(dk, key, hash);
1749+
1750+
if (ix >= 0) {
1751+
*value = mp->ma_values->values[ix];
1752+
}
1753+
else if (ix == DKIX_KEY_CHANGED) {
1754+
ix = _Py_dict_lookup(mp, key, hash, value);
1755+
}
1756+
else {
1757+
*value = 0;
1758+
}
1759+
return ix;
1760+
}
1761+
1762+
#endif
1763+
17291764
/*
17301765
Internal routine to insert a new item into the table.
17311766
Used both by the internal resize routine and by the public insert routine.
@@ -1757,17 +1792,7 @@ insertdict(PyInterpreterState *interp, PyDictObject *mp,
17571792
// half of _Py_dict_lookup_threadsafe and avoids locking the
17581793
// shared keys if we can
17591794
assert(PyUnicode_CheckExact(key));
1760-
ix = unicodekeys_lookup_unicode_threadsafe(dk, key, hash);
1761-
1762-
if (ix >= 0) {
1763-
old_value = mp->ma_values->values[ix];
1764-
}
1765-
else if (ix == DKIX_KEY_CHANGED) {
1766-
ix = _Py_dict_lookup(mp, key, hash, &old_value);
1767-
}
1768-
else {
1769-
old_value = NULL;
1770-
}
1795+
ix = splitdict_lookup_threadsafe(mp, dk, key, hash, &old_value);
17711796
} else {
17721797
ix = _Py_dict_lookup(mp, key, hash, &old_value);
17731798
}
@@ -1780,20 +1805,47 @@ insertdict(PyInterpreterState *interp, PyDictObject *mp,
17801805
MAINTAIN_TRACKING(mp, key, value);
17811806

17821807
if (ix == DKIX_EMPTY) {
1783-
uint64_t new_version = _PyDict_NotifyEvent(
1784-
interp, PyDict_EVENT_ADDED, mp, key, value);
1785-
/* Insert into new slot. */
1786-
mp->ma_keys->dk_version = 0;
1787-
assert(old_value == NULL);
1788-
1808+
uint64_t new_version;
17891809
if (!_PyDict_HasSplitTable(mp)) {
1810+
new_version = _PyDict_NotifyEvent(
1811+
interp, PyDict_EVENT_ADDED, mp, key, value);
1812+
/* Insert into new slot. */
1813+
mp->ma_keys->dk_version = 0;
17901814
if (insert_combined_dict(interp, mp, hash, key, value) < 0) {
17911815
goto Fail;
17921816
}
17931817
}
17941818
else {
1795-
if (insert_split_dict(interp, mp, hash, key, value) < 0)
1796-
goto Fail;
1819+
LOCK_KEYS(mp->ma_keys);
1820+
1821+
#ifdef Py_GIL_DISABLED
1822+
// We could have raced between our lookup before and the insert,
1823+
// so we need to lookup again with the keys locked
1824+
ix = splitdict_lookup_threadsafe(mp, dk, key, hash, &old_value);
1825+
if (ix >= 0) {
1826+
UNLOCK_KEYS(mp->ma_keys);
1827+
goto insert_on_split_race;
1828+
}
1829+
#endif
1830+
new_version = _PyDict_NotifyEvent(
1831+
interp, PyDict_EVENT_ADDED, mp, key, value);
1832+
/* Insert into new slot. */
1833+
mp->ma_keys->dk_version = 0;
1834+
if (mp->ma_keys->dk_usable <= 0) {
1835+
UNLOCK_KEYS(mp->ma_keys);
1836+
1837+
if (split_dict_resize_and_insert(interp, mp, hash, key, value) < 0) {
1838+
goto Fail;
1839+
}
1840+
} else {
1841+
int insert = insert_split_dict(interp, mp, hash, key, value);
1842+
UNLOCK_KEYS(mp->ma_keys);
1843+
1844+
if (insert < 0) {
1845+
goto Fail;
1846+
}
1847+
}
1848+
mp->ma_keys->dk_version = new_version;
17971849
}
17981850

17991851
mp->ma_used++;
@@ -1802,6 +1854,9 @@ insertdict(PyInterpreterState *interp, PyDictObject *mp,
18021854
return 0;
18031855
}
18041856

1857+
#ifdef Py_GIL_DISABLED
1858+
insert_on_split_race:
1859+
#endif
18051860
if (old_value != value) {
18061861
uint64_t new_version = _PyDict_NotifyEvent(
18071862
interp, PyDict_EVENT_MODIFIED, mp, key, value);
@@ -4255,13 +4310,40 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu
42554310
}
42564311
}
42574312
else {
4258-
if (insert_split_dict(interp, mp, hash, Py_NewRef(key), Py_NewRef(value)) < 0) {
4259-
Py_DECREF(key);
4260-
Py_DECREF(value);
4261-
if (result) {
4262-
*result = NULL;
4313+
LOCK_KEYS(mp->ma_keys);
4314+
#ifdef Py_GIL_DISABLED
4315+
// We could have raced between our lookup before and the insert,
4316+
// so we need to lookup again with the keys locked
4317+
ix = _Py_dict_lookup(mp, key, hash, &value);
4318+
if (ix >= 0) {
4319+
UNLOCK_KEYS(mp->ma_keys);
4320+
if (value != NULL) {
4321+
if (result) {
4322+
*result = incref_result ? Py_NewRef(value) : value;
4323+
}
4324+
return 0;
4325+
}
4326+
goto insert_on_split_race;
4327+
}
4328+
#endif
4329+
if (mp->ma_keys->dk_usable <= 0) {
4330+
UNLOCK_KEYS(mp->ma_keys);
4331+
4332+
if (split_dict_resize_and_insert(interp, mp, hash, key, value) < 0) {
4333+
return -1;
4334+
}
4335+
} else {
4336+
int insert = insert_split_dict(interp, mp, hash, Py_NewRef(key), Py_NewRef(value));
4337+
UNLOCK_KEYS(mp->ma_keys);
4338+
4339+
if (insert < 0) {
4340+
Py_DECREF(key);
4341+
Py_DECREF(value);
4342+
if (result) {
4343+
*result = NULL;
4344+
}
4345+
return -1;
42634346
}
4264-
return -1;
42654347
}
42664348
}
42674349

@@ -4276,6 +4358,9 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu
42764358
return 0;
42774359
}
42784360
else if (value == NULL) {
4361+
#ifdef Py_GIL_DISABLED
4362+
insert_on_split_race:
4363+
#endif
42794364
uint64_t new_version = _PyDict_NotifyEvent(
42804365
interp, PyDict_EVENT_ADDED, mp, key, default_value);
42814366
value = default_value;

0 commit comments

Comments
 (0)
0