gh-154523: Fix data-race in `TextIOWrapper.detach()` (#154565)
kumaraditya303 <[email protected]> Tue, 04 Aug 2026 09:30:08 -0400 (EDT)
| Newsgroups | gmane.comp.python.cvs |
|---|---|
| Message-ID | <[email protected]> |
https://github.com/python/cpython/commit/8440a6a4651f93ecd1c7768ffb2c735c5b9fb984 commit: 8440a6a4651f93ecd1c7768ffb2c735c5b9fb984 branch: main author: Brij Kapadia <[email protected]> committer: kumaraditya303 <[email protected]> date: 2026-08-04T18:59:52+05:30 summary: gh-154523: Fix data-race in `TextIOWrapper.detach()` (#154565) files: A Misc/NEWS.d/next/Library/2026-07-23-18-53-48.gh-issue-154523.XUSRBO.rst M Lib/test/test_free_threading/test_io.py M Modules/_io/clinic/textio.c.h M Modules/_io/textio.c diff --git a/Lib/test/test_free_threading/test_io.py b/Lib/test/test_free_threading/test_io.py index 057e0adf3b42bc4..a2b1ec8eb72bb1a 100644 --- a/Lib/test/test_free_threading/test_io.py +++ b/Lib/test/test_free_threading/test_io.py @@ -232,3 +232,23 @@ def reset_worker(): 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 000000000000000..23b25a26effb642 --- /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 8d59bda5f74b386..3c682cb2f271aef 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 5b2a20a30c28cb2..ea8ed2713d8a146 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} }; _______________________________________________ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3//lists/python-checkins.python.org Member address: [email protected]