Skip to content

Commit 3ec719f

Browse files
authored
gh-117657: Fix TSan race in _PyDict_CheckConsistency (#121551)
The only remaining race in dictobject.c was in _PyDict_CheckConsistency when the dictionary has shared keys.
1 parent 3bfc9c8 commit 3ec719f

File tree

2 files changed

+15
-17
lines changed

2 files changed

+15
-17
lines changed

Objects/dictobject.c

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -169,16 +169,15 @@ ASSERT_DICT_LOCKED(PyObject *op)
169169
#define STORE_INDEX(keys, size, idx, value) _Py_atomic_store_int##size##_relaxed(&((int##size##_t*)keys->dk_indices)[idx], (int##size##_t)value);
170170
#define ASSERT_OWNED_OR_SHARED(mp) \
171171
assert(_Py_IsOwnedByCurrentThread((PyObject *)mp) || IS_DICT_SHARED(mp));
172-
#define LOAD_KEYS_NENTRIES(d)
173172

174173
#define LOCK_KEYS_IF_SPLIT(keys, kind) \
175174
if (kind == DICT_KEYS_SPLIT) { \
176-
LOCK_KEYS(dk); \
175+
LOCK_KEYS(keys); \
177176
}
178177

179178
#define UNLOCK_KEYS_IF_SPLIT(keys, kind) \
180179
if (kind == DICT_KEYS_SPLIT) { \
181-
UNLOCK_KEYS(dk); \
180+
UNLOCK_KEYS(keys); \
182181
}
183182

184183
static inline Py_ssize_t
@@ -212,7 +211,7 @@ set_values(PyDictObject *mp, PyDictValues *values)
212211
#define INCREF_KEYS(dk) _Py_atomic_add_ssize(&dk->dk_refcnt, 1)
213212
// Dec refs the keys object, giving the previous value
214213
#define DECREF_KEYS(dk) _Py_atomic_add_ssize(&dk->dk_refcnt, -1)
215-
#define LOAD_KEYS_NENTIRES(keys) _Py_atomic_load_ssize_relaxed(&keys->dk_nentries)
214+
#define LOAD_KEYS_NENTRIES(keys) _Py_atomic_load_ssize_relaxed(&keys->dk_nentries)
216215

217216
#define INCREF_KEYS_FT(dk) dictkeys_incref(dk)
218217
#define DECREF_KEYS_FT(dk, shared) dictkeys_decref(_PyInterpreterState_GET(), dk, shared)
@@ -239,7 +238,7 @@ static inline void split_keys_entry_added(PyDictKeysObject *keys)
239238
#define STORE_SHARED_KEY(key, value) key = value
240239
#define INCREF_KEYS(dk) dk->dk_refcnt++
241240
#define DECREF_KEYS(dk) dk->dk_refcnt--
242-
#define LOAD_KEYS_NENTIRES(keys) keys->dk_nentries
241+
#define LOAD_KEYS_NENTRIES(keys) keys->dk_nentries
243242
#define INCREF_KEYS_FT(dk)
244243
#define DECREF_KEYS_FT(dk, shared)
245244
#define LOCK_KEYS_IF_SPLIT(keys, kind)
@@ -694,10 +693,15 @@ _PyDict_CheckConsistency(PyObject *op, int check_content)
694693
int splitted = _PyDict_HasSplitTable(mp);
695694
Py_ssize_t usable = USABLE_FRACTION(DK_SIZE(keys));
696695

696+
// In the free-threaded build, shared keys may be concurrently modified,
697+
// so use atomic loads.
698+
Py_ssize_t dk_usable = FT_ATOMIC_LOAD_SSIZE_ACQUIRE(keys->dk_usable);
699+
Py_ssize_t dk_nentries = FT_ATOMIC_LOAD_SSIZE_ACQUIRE(keys->dk_nentries);
700+
697701
CHECK(0 <= mp->ma_used && mp->ma_used <= usable);
698-
CHECK(0 <= keys->dk_usable && keys->dk_usable <= usable);
699-
CHECK(0 <= keys->dk_nentries && keys->dk_nentries <= usable);
700-
CHECK(keys->dk_usable + keys->dk_nentries <= usable);
702+
CHECK(0 <= dk_usable && dk_usable <= usable);
703+
CHECK(0 <= dk_nentries && dk_nentries <= usable);
704+
CHECK(dk_usable + dk_nentries <= usable);
701705

702706
if (!splitted) {
703707
/* combined table */
@@ -714,6 +718,7 @@ _PyDict_CheckConsistency(PyObject *op, int check_content)
714718
}
715719

716720
if (check_content) {
721+
LOCK_KEYS_IF_SPLIT(keys, keys->dk_kind);
717722
for (Py_ssize_t i=0; i < DK_SIZE(keys); i++) {
718723
Py_ssize_t ix = dictkeys_get_index(keys, i);
719724
CHECK(DKIX_DUMMY <= ix && ix <= usable);
@@ -769,6 +774,7 @@ _PyDict_CheckConsistency(PyObject *op, int check_content)
769774
CHECK(mp->ma_values->values[index] != NULL);
770775
}
771776
}
777+
UNLOCK_KEYS_IF_SPLIT(keys, keys->dk_kind);
772778
}
773779
return 1;
774780

@@ -4037,7 +4043,7 @@ dict_equal_lock_held(PyDictObject *a, PyDictObject *b)
40374043
/* can't be equal if # of entries differ */
40384044
return 0;
40394045
/* Same # of entries -- check all of 'em. Exit early on any diff. */
4040-
for (i = 0; i < LOAD_KEYS_NENTIRES(a->ma_keys); i++) {
4046+
for (i = 0; i < LOAD_KEYS_NENTRIES(a->ma_keys); i++) {
40414047
PyObject *key, *aval;
40424048
Py_hash_t hash;
40434049
if (DK_IS_UNICODE(a->ma_keys)) {

Tools/tsan/suppressions_free_threading.txt

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,8 @@ race_top:_add_to_weak_set
2727
race_top:_in_weak_set
2828
race_top:_PyEval_EvalFrameDefault
2929
race_top:assign_version_tag
30-
race_top:insertdict
31-
race_top:lookup_tp_dict
3230
race_top:new_reference
33-
race_top:_PyDict_CheckConsistency
34-
race_top:_Py_dict_lookup_threadsafe
3531
race_top:_multiprocessing_SemLock_acquire_impl
36-
race_top:dictiter_new
37-
race_top:dictresize
38-
race_top:insert_to_emptydict
39-
race_top:insertdict
4032
race_top:list_get_item_ref
4133
race_top:make_pending_calls
4234
race_top:_Py_slot_tp_getattr_hook

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy