From d7bac886377abf7ec950cfc304ae71df054d8420 Mon Sep 17 00:00:00 2001 From: LYNLE Date: Sun, 19 Jul 2026 12:59:34 +0200 Subject: [PATCH 01/11] improve thread safety --- Objects/enumobject.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Objects/enumobject.c b/Objects/enumobject.c index fc53f1bfee8dde..c0c4c1c0b9b4ab 100644 --- a/Objects/enumobject.c +++ b/Objects/enumobject.c @@ -277,10 +277,13 @@ enum_reduce(PyObject *op, PyObject *Py_UNUSED(ignored)) enumobject *en = _enumobject_CAST(op); PyObject *result; Py_BEGIN_CRITICAL_SECTION(en); - if (en->en_longindex != NULL) + if (en->en_longindex != NULL) { result = Py_BuildValue("O(OO)", Py_TYPE(en), en->en_sit, en->en_longindex); - else + } + else { + Py_ssize_t en_index = FT_ATOMIC_LOAD_SSIZE_RELAXED(en->en_index); result = Py_BuildValue("O(On)", Py_TYPE(en), en->en_sit, en->en_index); + } Py_END_CRITICAL_SECTION(); return result; } From 6777fa8254410c5705e1cf45dc5bf6629b1ff751 Mon Sep 17 00:00:00 2001 From: LYNLE Date: Sun, 19 Jul 2026 13:33:50 +0200 Subject: [PATCH 02/11] bugfix & added ThreadSafety class to test_enumerate.py --- Lib/test/test_enumerate.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/Lib/test/test_enumerate.py b/Lib/test/test_enumerate.py index 5cb54cff9b76fd..a7caccd7512121 100644 --- a/Lib/test/test_enumerate.py +++ b/Lib/test/test_enumerate.py @@ -3,6 +3,9 @@ import sys import pickle import gc +import itertools +import threading + from test import support @@ -292,5 +295,34 @@ def enum(self, iterable, start=sys.maxsize + 1): (sys.maxsize+3,'c')] + +class ThreadSafety(EnumerateStartTestCase): + def test_thread_safety_while_iterating(self): + en = enumerate(itertools.count()) # infinite inner iterator: next() never ends + stop = threading.Event() + + def advance(): + while not stop.is_set(): + next(en) # enum_next: atomic write of en_index + + + def read(): + while not stop.is_set(): + en.__reduce__() # enum_reduce: plain read of en_index + + + threads = [ + threading.Thread(target=advance), + threading.Thread(target=read) + ] + + for t in threads: + t.start() + threading.Event().wait(2.0) + stop.set() + for t in threads: + t.join() + + if __name__ == "__main__": unittest.main() From ed8a46ae4b0a8631f02eb28d0ad68b3bd5c7a858 Mon Sep 17 00:00:00 2001 From: LYNLE Date: Sun, 19 Jul 2026 14:01:04 +0200 Subject: [PATCH 03/11] remove en-> & add ThreadSafety test in test_enumerate.py --- Lib/test/test_enumerate.py | 17 +++++++---------- Objects/enumobject.c | 2 +- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/Lib/test/test_enumerate.py b/Lib/test/test_enumerate.py index a7caccd7512121..05bbb8d107250e 100644 --- a/Lib/test/test_enumerate.py +++ b/Lib/test/test_enumerate.py @@ -3,7 +3,6 @@ import sys import pickle import gc -import itertools import threading @@ -298,12 +297,15 @@ def enum(self, iterable, start=sys.maxsize + 1): class ThreadSafety(EnumerateStartTestCase): def test_thread_safety_while_iterating(self): - en = enumerate(itertools.count()) # infinite inner iterator: next() never ends + range_stop = 10_000 + en = enumerate(range(1, range_stop)) # next() ends after specified range stop = threading.Event() + def advance(): - while not stop.is_set(): + for _ in range(1, range_stop -1): next(en) # enum_next: atomic write of en_index + stop.set() def read(): @@ -311,15 +313,10 @@ def read(): en.__reduce__() # enum_reduce: plain read of en_index - threads = [ - threading.Thread(target=advance), - threading.Thread(target=read) - ] - + threads = [threading.Thread(target=advance), threading.Thread(target=read)] for t in threads: t.start() - threading.Event().wait(2.0) - stop.set() + for t in threads: t.join() diff --git a/Objects/enumobject.c b/Objects/enumobject.c index c0c4c1c0b9b4ab..68aa594c5540ce 100644 --- a/Objects/enumobject.c +++ b/Objects/enumobject.c @@ -282,7 +282,7 @@ enum_reduce(PyObject *op, PyObject *Py_UNUSED(ignored)) } else { Py_ssize_t en_index = FT_ATOMIC_LOAD_SSIZE_RELAXED(en->en_index); - result = Py_BuildValue("O(On)", Py_TYPE(en), en->en_sit, en->en_index); + result = Py_BuildValue("O(On)", Py_TYPE(en), en->en_sit, en_index); } Py_END_CRITICAL_SECTION(); return result; From 323418ab4b0961f99c2c79d7f11cdda2957de1dd Mon Sep 17 00:00:00 2001 From: LYNLE Date: Sun, 19 Jul 2026 14:01:39 +0200 Subject: [PATCH 04/11] add test_enumerate to TSAN_TESTS --- Lib/test/libregrtest/tsan.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/libregrtest/tsan.py b/Lib/test/libregrtest/tsan.py index bacfe5e21ba0b7..c7ec63763bc9d8 100644 --- a/Lib/test/libregrtest/tsan.py +++ b/Lib/test/libregrtest/tsan.py @@ -8,6 +8,7 @@ 'test_ctypes', 'test_concurrent_futures', 'test_enum', + 'test_enumerate', 'test_functools', 'test_httpservers', 'test_imaplib', From b85302c417575c3ce4b0235fa996b7a56fdd7ce7 Mon Sep 17 00:00:00 2001 From: LYNLE Date: Sun, 19 Jul 2026 14:14:22 +0200 Subject: [PATCH 05/11] using threading_helper to start threads --- Lib/test/test_enumerate.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_enumerate.py b/Lib/test/test_enumerate.py index 05bbb8d107250e..9420835708cfb3 100644 --- a/Lib/test/test_enumerate.py +++ b/Lib/test/test_enumerate.py @@ -7,6 +7,7 @@ from test import support +from test.support import threading_helper class G: 'Sequence using __getitem__' @@ -297,28 +298,25 @@ def enum(self, iterable, start=sys.maxsize + 1): class ThreadSafety(EnumerateStartTestCase): def test_thread_safety_while_iterating(self): - range_stop = 10_000 - en = enumerate(range(1, range_stop)) # next() ends after specified range - stop = threading.Event() + # gh-153932: calling reduce while iterating should pass with TSAN + en = enumerate(range(10_000)) + stop = threading.Event() def advance(): - for _ in range(1, range_stop -1): - next(en) # enum_next: atomic write of en_index + for _ in en: + pass stop.set() - def read(): while not stop.is_set(): - en.__reduce__() # enum_reduce: plain read of en_index - + en.__reduce__() threads = [threading.Thread(target=advance), threading.Thread(target=read)] - for t in threads: - t.start() - for t in threads: - t.join() + with threading_helper.start_threads(threads): + pass + if __name__ == "__main__": From 6aa64bc431f7336db24656867ab648117297345c Mon Sep 17 00:00:00 2001 From: LYNLE Date: Sun, 19 Jul 2026 14:20:45 +0200 Subject: [PATCH 06/11] remove spaces --- Lib/test/test_enumerate.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_enumerate.py b/Lib/test/test_enumerate.py index 9420835708cfb3..9949e635f5920c 100644 --- a/Lib/test/test_enumerate.py +++ b/Lib/test/test_enumerate.py @@ -300,17 +300,17 @@ class ThreadSafety(EnumerateStartTestCase): def test_thread_safety_while_iterating(self): # gh-153932: calling reduce while iterating should pass with TSAN - en = enumerate(range(10_000)) + en = enumerate(range(10_000)) stop = threading.Event() def advance(): for _ in en: - pass + pass stop.set() def read(): while not stop.is_set(): - en.__reduce__() + en.__reduce__() threads = [threading.Thread(target=advance), threading.Thread(target=read)] From a96994646484938c0be22f7fd84395dbf16ad543 Mon Sep 17 00:00:00 2001 From: LYNLE Date: Sun, 19 Jul 2026 14:32:32 +0200 Subject: [PATCH 07/11] typo: ThreadSafety -> TestThreadSafety --- Lib/test/test_enumerate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_enumerate.py b/Lib/test/test_enumerate.py index 9949e635f5920c..2ad084b414add0 100644 --- a/Lib/test/test_enumerate.py +++ b/Lib/test/test_enumerate.py @@ -296,7 +296,7 @@ def enum(self, iterable, start=sys.maxsize + 1): -class ThreadSafety(EnumerateStartTestCase): +class TestThreadSafety(EnumerateStartTestCase): def test_thread_safety_while_iterating(self): # gh-153932: calling reduce while iterating should pass with TSAN From 77da047893c7da49c2f8237ac7e1b1e193cfadfa Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sun, 19 Jul 2026 12:33:29 +0000 Subject: [PATCH 08/11] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Security/2026-07-19-12-33-27.gh-issue-153932.lKw2bo.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Security/2026-07-19-12-33-27.gh-issue-153932.lKw2bo.rst diff --git a/Misc/NEWS.d/next/Security/2026-07-19-12-33-27.gh-issue-153932.lKw2bo.rst b/Misc/NEWS.d/next/Security/2026-07-19-12-33-27.gh-issue-153932.lKw2bo.rst new file mode 100644 index 00000000000000..24c08fadce01e2 --- /dev/null +++ b/Misc/NEWS.d/next/Security/2026-07-19-12-33-27.gh-issue-153932.lKw2bo.rst @@ -0,0 +1 @@ +Fix thread safety issue in enumerate.__reduce__ by protecting the read of ->en_index. Also added `TestThreadSafety` to test_enumerate.py. From e4ad6450742742dc14f55e38ca17fcef027a0b3a Mon Sep 17 00:00:00 2001 From: LYNLE Date: Sun, 19 Jul 2026 14:41:26 +0200 Subject: [PATCH 09/11] threading_helper decorator & whitespace --- Lib/test/test_enumerate.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/test/test_enumerate.py b/Lib/test/test_enumerate.py index 2ad084b414add0..c8b85fe8692178 100644 --- a/Lib/test/test_enumerate.py +++ b/Lib/test/test_enumerate.py @@ -295,7 +295,7 @@ def enum(self, iterable, start=sys.maxsize + 1): (sys.maxsize+3,'c')] - +@threading_helper.requires_working_threading() class TestThreadSafety(EnumerateStartTestCase): def test_thread_safety_while_iterating(self): # gh-153932: calling reduce while iterating should pass with TSAN @@ -316,7 +316,6 @@ def read(): with threading_helper.start_threads(threads): pass - if __name__ == "__main__": From fb7242d5e98c1c67b6a51596c034eae0e8111cdd Mon Sep 17 00:00:00 2001 From: LYNLE Date: Sun, 19 Jul 2026 14:45:26 +0200 Subject: [PATCH 10/11] change NEWS category from Security to Core_and_Builtins --- .../2026-07-19-12-33-27.gh-issue-153932.lKw2bo.rst | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Misc/NEWS.d/next/{Security => Core_and_Builtins}/2026-07-19-12-33-27.gh-issue-153932.lKw2bo.rst (100%) diff --git a/Misc/NEWS.d/next/Security/2026-07-19-12-33-27.gh-issue-153932.lKw2bo.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-19-12-33-27.gh-issue-153932.lKw2bo.rst similarity index 100% rename from Misc/NEWS.d/next/Security/2026-07-19-12-33-27.gh-issue-153932.lKw2bo.rst rename to Misc/NEWS.d/next/Core_and_Builtins/2026-07-19-12-33-27.gh-issue-153932.lKw2bo.rst From a4fcaa9d71a224902c386186b6e2c7c34530477a Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Sun, 19 Jul 2026 14:49:19 +0200 Subject: [PATCH 11/11] Apply NEWS suggestion --- .../2026-07-19-12-33-27.gh-issue-153932.lKw2bo.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-19-12-33-27.gh-issue-153932.lKw2bo.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-19-12-33-27.gh-issue-153932.lKw2bo.rst index 24c08fadce01e2..56b96307ed04cd 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-19-12-33-27.gh-issue-153932.lKw2bo.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-19-12-33-27.gh-issue-153932.lKw2bo.rst @@ -1 +1,2 @@ -Fix thread safety issue in enumerate.__reduce__ by protecting the read of ->en_index. Also added `TestThreadSafety` to test_enumerate.py. +Fix thread safety issue in the ``__reduce__`` method of +:py:class:`enumerate`.