Skip to content

Commit 2e206ac

Browse files
authored
Merge branch 'main' into main
2 parents 829b200 + b8e925b commit 2e206ac

File tree

4 files changed

+39
-3
lines changed

4 files changed

+39
-3
lines changed

Lib/functools.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,7 @@ def _unwrap_partialmethod(func):
517517
### LRU Cache function decorator
518518
################################################################################
519519

520-
_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])
520+
_CacheInfo = namedtuple("CacheInfo", ("hits", "misses", "maxsize", "currsize"))
521521

522522
def _make_key(args, kwds, typed,
523523
kwd_mark = (object(),),
@@ -539,13 +539,15 @@ def _make_key(args, kwds, typed,
539539
# distinct call from f(y=2, x=1) which will be cached separately.
540540
key = args
541541
if kwds:
542+
key = list(key)
542543
key += kwd_mark
543544
for item in kwds.items():
544545
key += item
546+
key = tuple(key)
545547
if typed:
546-
key += tuple(type(v) for v in args)
548+
key += tuple([type(v) for v in args])
547549
if kwds:
548-
key += tuple(type(v) for v in kwds.values())
550+
key += tuple([type(v) for v in kwds.values()])
549551
elif len(key) == 1 and type(key[0]) in fasttypes:
550552
return key[0]
551553
return key

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)