]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-154523: Fix data-race in `TextIOWrapper.detach()` (#154565) main
authorBrij Kapadia <97006829+brijkapadia@users.noreply.github.com>
Tue, 4 Aug 2026 13:29:52 +0000 (09:29 -0400)
committerGitHub <noreply@github.com>
Tue, 4 Aug 2026 13:29:52 +0000 (18:59 +0530)
Lib/test/test_free_threading/test_io.py
Misc/NEWS.d/next/Library/2026-07-23-18-53-48.gh-issue-154523.XUSRBO.rst [new file with mode: 0644]
Modules/_io/clinic/textio.c.h
Modules/_io/textio.c

index 057e0adf3b42bc45cba69c3f0f8d0ed11e843663..a2b1ec8eb72bb1afc6739896ba4c83991c269c1b 100644 (file)
@@ -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 (file)
index 0000000..23b25a2
--- /dev/null
@@ -0,0 +1,2 @@
+Fixed data-race when calling :meth:`io.TextIOBase.detach` in
+:term:`free-threaded build`.
index 8d59bda5f74b3861a75d8cb54ad229e4cdc4da81..3c682cb2f271aef7ddc49d599855e5385db936c9 100644 (file)
@@ -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]*/
index 5b2a20a30c28cb24689b8fb1b0e6030b9dd1ff47..ea8ed2713d8a1464d386d563bc2079fdf0011653 100644 (file)
@@ -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}
 };