From: Brij Kapadia <97006829+brijkapadia@users.noreply.github.com> Date: Tue, 4 Aug 2026 13:29:52 +0000 (-0400) Subject: gh-154523: Fix data-race in `TextIOWrapper.detach()` (#154565) X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=8440a6a4651f93ecd1c7768ffb2c735c5b9fb984;p=thirdparty%2FPython%2Fcpython.git gh-154523: Fix data-race in `TextIOWrapper.detach()` (#154565) --- diff --git a/Lib/test/test_free_threading/test_io.py b/Lib/test/test_free_threading/test_io.py index 057e0adf3b42..a2b1ec8eb72b 100644 --- a/Lib/test/test_free_threading/test_io.py +++ b/Lib/test/test_free_threading/test_io.py @@ -232,3 +232,23 @@ class IncrementalNewlineDecoderTest(TestCase): decoder.reset() run_concurrently([decode_worker] * 2 + [reset_worker] * 2) + + +class TextIOWrapperTest(TestCase): + def test_buffer_detach_race(self): + make = lambda: io.TextIOWrapper(io.BytesIO()) + slot = [make()] + + def reader(): + for _ in range(1000): + try: + slot[0].buffer + except ValueError: + pass + + def detacher(): + for _ in range(1000): + slot[0] = make() + slot[0].detach() + + run_concurrently([reader, detacher]) diff --git a/Misc/NEWS.d/next/Library/2026-07-23-18-53-48.gh-issue-154523.XUSRBO.rst b/Misc/NEWS.d/next/Library/2026-07-23-18-53-48.gh-issue-154523.XUSRBO.rst new file mode 100644 index 000000000000..23b25a26effb --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-23-18-53-48.gh-issue-154523.XUSRBO.rst @@ -0,0 +1,2 @@ +Fixed data-race when calling :meth:`io.TextIOBase.detach` in +:term:`free-threaded build`. diff --git a/Modules/_io/clinic/textio.c.h b/Modules/_io/clinic/textio.c.h index 8d59bda5f74b..3c682cb2f271 100644 --- a/Modules/_io/clinic/textio.c.h +++ b/Modules/_io/clinic/textio.c.h @@ -1331,4 +1331,29 @@ _io_TextIOWrapper__CHUNK_SIZE_set(PyObject *self, PyObject *value, void *Py_UNUS return return_value; } -/*[clinic end generated code: output=8c571c9dba87d2b1 input=a9049054013a1b77]*/ + +#if !defined(_io_TextIOWrapper_buffer_DOCSTR) +# define _io_TextIOWrapper_buffer_DOCSTR NULL +#endif +#if defined(_IO_TEXTIOWRAPPER_BUFFER_GETSETDEF) +# undef _IO_TEXTIOWRAPPER_BUFFER_GETSETDEF +# define _IO_TEXTIOWRAPPER_BUFFER_GETSETDEF {"buffer", (getter)_io_TextIOWrapper_buffer_get, (setter)_io_TextIOWrapper_buffer_set, _io_TextIOWrapper_buffer_DOCSTR}, +#else +# define _IO_TEXTIOWRAPPER_BUFFER_GETSETDEF {"buffer", (getter)_io_TextIOWrapper_buffer_get, NULL, _io_TextIOWrapper_buffer_DOCSTR}, +#endif + +static PyObject * +_io_TextIOWrapper_buffer_get_impl(textio *self); + +static PyObject * +_io_TextIOWrapper_buffer_get(PyObject *self, void *Py_UNUSED(context)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _io_TextIOWrapper_buffer_get_impl((textio *)self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} +/*[clinic end generated code: output=e34c75e1d2a12084 input=a9049054013a1b77]*/ diff --git a/Modules/_io/textio.c b/Modules/_io/textio.c index 5b2a20a30c28..ea8ed2713d8a 100644 --- a/Modules/_io/textio.c +++ b/Modules/_io/textio.c @@ -3424,6 +3424,19 @@ _io_TextIOWrapper__CHUNK_SIZE_set_impl(textio *self, PyObject *value) return 0; } +/*[clinic input] +@critical_section +@getter +_io.TextIOWrapper.buffer +[clinic start generated code]*/ + +static PyObject * +_io_TextIOWrapper_buffer_get_impl(textio *self) +/*[clinic end generated code: output=d265a34555aa5d4b input=5951cfa148f7350a]*/ +{ + return Py_XNewRef(buffer_access_safe(self)); +} + static PyMethodDef incrementalnewlinedecoder_methods[] = { _IO_INCREMENTALNEWLINEDECODER_DECODE_METHODDEF _IO_INCREMENTALNEWLINEDECODER_GETSTATE_METHODDEF @@ -3482,7 +3495,6 @@ static PyMethodDef textiowrapper_methods[] = { static PyMemberDef textiowrapper_members[] = { {"encoding", _Py_T_OBJECT, offsetof(textio, encoding), Py_READONLY}, - {"buffer", _Py_T_OBJECT, offsetof(textio, buffer), Py_READONLY}, {"line_buffering", Py_T_BOOL, offsetof(textio, line_buffering), Py_READONLY}, {"write_through", Py_T_BOOL, offsetof(textio, write_through), Py_READONLY}, {"_finalizing", Py_T_BOOL, offsetof(textio, finalizing), 0}, @@ -3497,6 +3509,7 @@ static PyGetSetDef textiowrapper_getset[] = { _IO_TEXTIOWRAPPER_NEWLINES_GETSETDEF _IO_TEXTIOWRAPPER_ERRORS_GETSETDEF _IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF + _IO_TEXTIOWRAPPER_BUFFER_GETSETDEF {NULL} };