Skip to content

Commit 7264d66

Browse files
gh-141805: Fix crash after concurrent addition objects with the same hash to set
This happens when the set contained several elements with the same hash, and then some of them were removed.
1 parent 2873c31 commit 7264d66

3 files changed

Lines changed: 34 additions & 0 deletions

File tree

Lib/test/test_set.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1853,6 +1853,7 @@ def test_iter_and_mutate(self):
18531853
list(si)
18541854

18551855
def test_merge_and_mutate(self):
1856+
# gh-141805
18561857
class X:
18571858
def __hash__(self):
18581859
return hash(0)
@@ -1865,6 +1866,33 @@ def __eq__(self, o):
18651866
s = {0}
18661867
s.update(other)
18671868

1869+
def test_hash_collision_concurrent_add(self):
1870+
class X:
1871+
def __hash__(self):
1872+
return 0
1873+
class Y:
1874+
flag = False
1875+
def __hash__(self):
1876+
return 0
1877+
def __eq__(self, other):
1878+
if not self.flag:
1879+
self.flag = True
1880+
s.add(X())
1881+
return self is other
1882+
1883+
a = X()
1884+
s = set()
1885+
s.add(a)
1886+
s.add(X())
1887+
s.remove(a)
1888+
# Now the set contains a dummy entry followed by an entry
1889+
# for an object with hash 0.
1890+
s.add(Y())
1891+
# The following operations should not crash.
1892+
repr(s)
1893+
list(s)
1894+
set() | s
1895+
18681896

18691897
class TestOperationsMutating:
18701898
"""Regression test for bpo-46615"""
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix crash in :class:`set` when objects with the same hash are concurrently
2+
added to the set after removing an element with the same hash while the set
3+
still contains elements with the same hash.

Objects/setobject.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,9 @@ set_add_entry_takeref(PySetObject *so, PyObject *key, Py_hash_t hash)
308308
found_unused_or_dummy:
309309
if (freeslot == NULL)
310310
goto found_unused;
311+
if (freeslot->hash != -1) {
312+
goto restart;
313+
}
311314
FT_ATOMIC_STORE_SSIZE_RELAXED(so->used, so->used + 1);
312315
FT_ATOMIC_STORE_SSIZE_RELAXED(freeslot->hash, hash);
313316
FT_ATOMIC_STORE_PTR_RELEASE(freeslot->key, key);

0 commit comments

Comments
 (0)