diff --git a/Lib/test/test_free_threading/test_set.py b/Lib/test/test_free_threading/test_set.py index 9dd3d68d5dad13..1d5e09cb4089d4 100644 --- a/Lib/test/test_free_threading/test_set.py +++ b/Lib/test/test_free_threading/test_set.py @@ -148,6 +148,32 @@ def read_set(): for t in threads: t.join() + def test_iter_length_hint_mutate(self): + s = set(range(2000)) + it = iter(s) + stop = Event() + + def reader(): + while not stop.is_set(): + it.__length_hint__() + + def writer(): + i = 0 + while not stop.is_set(): + s.add(i) + s.discard(i - 1) + i += 1 + + threads = [Thread(target=reader) for _ in range(4)] + threads.append(Thread(target=writer)) + + for t in threads: + t.start() + + stop.set() + + for t in threads: + t.join() @threading_helper.requires_working_threading() class SmallSetTest(RaceTestBase, unittest.TestCase): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-01-31-03-40-28.gh-issue-144356.otfq_X.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-01-31-03-40-28.gh-issue-144356.otfq_X.rst new file mode 100644 index 00000000000000..d5d67ad1e8dbb3 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-01-31-03-40-28.gh-issue-144356.otfq_X.rst @@ -0,0 +1 @@ +Fix a data race in ``set_iterator.__length_hint__`` under ``Py_GIL_DISABLED``. diff --git a/Objects/setobject.c b/Objects/setobject.c index 5d4d1812282eed..85001c980b6f29 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -1056,7 +1056,8 @@ setiter_len(PyObject *op, PyObject *Py_UNUSED(ignored)) { setiterobject *si = (setiterobject*)op; Py_ssize_t len = 0; - if (si->si_set != NULL && si->si_used == si->si_set->used) + PySetObject *so = si->si_set; + if (so != NULL && si->si_used == FT_ATOMIC_LOAD_SSIZE_RELAXED(so->used)) len = si->len; return PyLong_FromSsize_t(len); }