From 5f3825b8baface39cb40038a0b81f13fa6d6601b Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Wed, 11 Jun 2025 19:05:02 -0400 Subject: [PATCH 1/3] Fix thread safety in StringIO. --- Modules/_io/stringio.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Modules/_io/stringio.c b/Modules/_io/stringio.c index 56913fafefba8b..9cf1e0e3e7c278 100644 --- a/Modules/_io/stringio.c +++ b/Modules/_io/stringio.c @@ -79,6 +79,7 @@ static int _io_StringIO___init__(PyObject *self, PyObject *args, PyObject *kwarg static int resize_buffer(stringio *self, size_t size) { + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self); /* Here, unsigned types are used to avoid dealing with signed integer overflow, which is undefined in C. */ size_t alloc = self->buf_size; @@ -131,6 +132,7 @@ resize_buffer(stringio *self, size_t size) static PyObject * make_intermediate(stringio *self) { + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self); PyObject *intermediate = PyUnicodeWriter_Finish(self->writer); self->writer = NULL; self->state = STATE_REALIZED; @@ -153,6 +155,7 @@ make_intermediate(stringio *self) static int realize(stringio *self) { + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self); Py_ssize_t len; PyObject *intermediate; @@ -188,6 +191,7 @@ realize(stringio *self) static Py_ssize_t write_str(stringio *self, PyObject *obj) { + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self); Py_ssize_t len; PyObject *decoded = NULL; @@ -355,6 +359,7 @@ _io_StringIO_read_impl(stringio *self, Py_ssize_t size) static PyObject * _stringio_readline(stringio *self, Py_ssize_t limit) { + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self); Py_UCS4 *start, *end, old_char; Py_ssize_t len, consumed; @@ -404,8 +409,9 @@ _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); @@ -441,6 +447,16 @@ stringio_iternext(PyObject *op) return line; } +static PyObject * +stringio_iternext(PyObject *op) +{ + PyObject *res; + Py_BEGIN_CRITICAL_SECTION(op); + res = stringio_iternext_lock_held(op); + Py_END_CRITICAL_SECTION(); + return res; +} + /*[clinic input] @critical_section _io.StringIO.truncate From 25d2f2723fe5ea711857ab8975b4e7ca5db3052c Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Wed, 11 Jun 2025 19:05:53 -0400 Subject: [PATCH 2/3] Add blurb. --- .../next/Library/2025-06-11-19-05-49.gh-issue-135410.E89Boi.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-06-11-19-05-49.gh-issue-135410.E89Boi.rst diff --git a/Misc/NEWS.d/next/Library/2025-06-11-19-05-49.gh-issue-135410.E89Boi.rst b/Misc/NEWS.d/next/Library/2025-06-11-19-05-49.gh-issue-135410.E89Boi.rst new file mode 100644 index 00000000000000..a5917fba3f7bb9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-06-11-19-05-49.gh-issue-135410.E89Boi.rst @@ -0,0 +1,2 @@ +Fix a crash when iterating over :class:`io.StringIO` on the :term:`free +threaded ` build. From 455bfba87b715c60e4a48dd26910823fef34d6be Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Wed, 11 Jun 2025 19:15:36 -0400 Subject: [PATCH 3/3] Add a test. --- Lib/test/test_memoryio.py | 19 +++++++++++++++++++ Modules/_io/stringio.c | 6 ------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_memoryio.py b/Lib/test/test_memoryio.py index 63998a86c45b53..249e0f3ba32f29 100644 --- a/Lib/test/test_memoryio.py +++ b/Lib/test/test_memoryio.py @@ -5,6 +5,7 @@ import unittest from test import support +from test.support import threading_helper import gc import io @@ -12,6 +13,7 @@ import pickle import sys import weakref +import threading class IntLike: def __init__(self, num): @@ -723,6 +725,22 @@ def test_newline_argument(self): for newline in (None, "", "\n", "\r", "\r\n"): self.ioclass(newline=newline) + @unittest.skipUnless(support.Py_GIL_DISABLED, "only meaningful under free-threading") + @threading_helper.requires_working_threading() + def test_concurrent_use(self): + memio = self.ioclass("") + + def use(): + memio.write("x" * 10) + memio.readlines() + + threads = [threading.Thread(target=use) for _ in range(8)] + with threading_helper.catch_threading_exception() as cm: + with threading_helper.start_threads(threads): + pass + + self.assertIsNone(cm.exc_value) + class PyStringIOTest(MemoryTestMixin, MemorySeekTestMixin, TextIOTestMixin, unittest.TestCase): @@ -890,6 +908,7 @@ def test_setstate(self): self.assertRaises(ValueError, memio.__setstate__, ("closed", "", 0, None)) + class CStringIOPickleTest(PyStringIOPickleTest): UnsupportedOperation = io.UnsupportedOperation diff --git a/Modules/_io/stringio.c b/Modules/_io/stringio.c index 9cf1e0e3e7c278..8482c268176c5b 100644 --- a/Modules/_io/stringio.c +++ b/Modules/_io/stringio.c @@ -79,7 +79,6 @@ static int _io_StringIO___init__(PyObject *self, PyObject *args, PyObject *kwarg static int resize_buffer(stringio *self, size_t size) { - _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self); /* Here, unsigned types are used to avoid dealing with signed integer overflow, which is undefined in C. */ size_t alloc = self->buf_size; @@ -132,7 +131,6 @@ resize_buffer(stringio *self, size_t size) static PyObject * make_intermediate(stringio *self) { - _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self); PyObject *intermediate = PyUnicodeWriter_Finish(self->writer); self->writer = NULL; self->state = STATE_REALIZED; @@ -155,7 +153,6 @@ make_intermediate(stringio *self) static int realize(stringio *self) { - _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self); Py_ssize_t len; PyObject *intermediate; @@ -191,7 +188,6 @@ realize(stringio *self) static Py_ssize_t write_str(stringio *self, PyObject *obj) { - _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self); Py_ssize_t len; PyObject *decoded = NULL; @@ -359,7 +355,6 @@ _io_StringIO_read_impl(stringio *self, Py_ssize_t size) static PyObject * _stringio_readline(stringio *self, Py_ssize_t limit) { - _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self); Py_UCS4 *start, *end, old_char; Py_ssize_t len, consumed; @@ -411,7 +406,6 @@ _io_StringIO_readline_impl(stringio *self, Py_ssize_t size) static PyObject * stringio_iternext_lock_held(PyObject *op) { - _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op); PyObject *line; stringio *self = stringio_CAST(op); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy