From 21fafe93980f8587914f446fff8173f69fc8d02d Mon Sep 17 00:00:00 2001 From: Twix1288 Date: Wed, 8 Jul 2026 15:28:36 -0700 Subject: [PATCH 1/3] gh-153296: Fix thread-safety data race in io.StringIO iterator --- .../2026-07-08-15-26-00.gh-issue-153296.Twix12.rst | 1 + Modules/_io/stringio.c | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-07-08-15-26-00.gh-issue-153296.Twix12.rst diff --git a/Misc/NEWS.d/next/Library/2026-07-08-15-26-00.gh-issue-153296.Twix12.rst b/Misc/NEWS.d/next/Library/2026-07-08-15-26-00.gh-issue-153296.Twix12.rst new file mode 100644 index 00000000000000..22604dd31dd605 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-08-15-26-00.gh-issue-153296.Twix12.rst @@ -0,0 +1 @@ +Fix a data race and use-after-free when iterating over an :class:`io.StringIO` object while it is being concurrently mutated. The ``__next__`` method now properly acquires the object's lock. diff --git a/Modules/_io/stringio.c b/Modules/_io/stringio.c index b8601383ad0a26..27605b4c44239e 100644 --- a/Modules/_io/stringio.c +++ b/Modules/_io/stringio.c @@ -407,8 +407,10 @@ _io_StringIO_readline_impl(stringio *self, Py_ssize_t size) } static PyObject * -stringio_iternext(PyObject *op) +stringio_iternext_lock_held(PyObject *op) { + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op); + PyObject *line; stringio *self = stringio_CAST(op); @@ -444,6 +446,16 @@ stringio_iternext(PyObject *op) return line; } +static PyObject * +stringio_iternext(PyObject *op) +{ + PyObject *ret; + Py_BEGIN_CRITICAL_SECTION(op); + ret = stringio_iternext_lock_held(op); + Py_END_CRITICAL_SECTION(); + return ret; +} + /*[clinic input] @critical_section _io.StringIO.truncate From 8ba4fe6612425546dc28fc2def473100ee60e3f3 Mon Sep 17 00:00:00 2001 From: Twix1288 Date: Wed, 8 Jul 2026 16:04:47 -0700 Subject: [PATCH 2/3] gh-153296: Fix flaky test_rollover_at_midnight on Windows --- Lib/test/test_logging.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index a7ea128d64055a..4cb40934cd8df7 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -6729,7 +6729,9 @@ def test_rollover_at_midnight(self, weekly=False): # The creation time, used to compute the rollover time, cannot be # changed, so the rollover cannot be forced by back-dating the file. # Wait until the clock reaches a rollover time set one second ahead. - rollover = int(time.time()) + 1 + stat = os.stat(self.fn) + file_time = int(min(getattr(stat, 'st_birthtime', stat.st_ctime), stat.st_mtime)) + rollover = max(int(time.time()), file_time) + 1 atTime = datetime.datetime.fromtimestamp(rollover).time() while time.time() < rollover: time.sleep(rollover - time.time()) From 1592fc9db8258c5023ee087c255736bf6886cf08 Mon Sep 17 00:00:00 2001 From: Twix1288 Date: Wed, 8 Jul 2026 16:06:38 -0700 Subject: [PATCH 3/3] Revert "gh-153296: Fix flaky test_rollover_at_midnight on Windows" This reverts commit 8ba4fe6612425546dc28fc2def473100ee60e3f3. --- Lib/test/test_logging.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index 4cb40934cd8df7..a7ea128d64055a 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -6729,9 +6729,7 @@ def test_rollover_at_midnight(self, weekly=False): # The creation time, used to compute the rollover time, cannot be # changed, so the rollover cannot be forced by back-dating the file. # Wait until the clock reaches a rollover time set one second ahead. - stat = os.stat(self.fn) - file_time = int(min(getattr(stat, 'st_birthtime', stat.st_ctime), stat.st_mtime)) - rollover = max(int(time.time()), file_time) + 1 + rollover = int(time.time()) + 1 atTime = datetime.datetime.fromtimestamp(rollover).time() while time.time() < rollover: time.sleep(rollover - time.time())