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/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. diff --git a/Modules/_io/stringio.c b/Modules/_io/stringio.c index 56913fafefba8b..8482c268176c5b 100644 --- a/Modules/_io/stringio.c +++ b/Modules/_io/stringio.c @@ -404,7 +404,7 @@ _io_StringIO_readline_impl(stringio *self, Py_ssize_t size) } static PyObject * -stringio_iternext(PyObject *op) +stringio_iternext_lock_held(PyObject *op) { PyObject *line; stringio *self = stringio_CAST(op); @@ -441,6 +441,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