https://github.com/python/cpython/commit/5dd3a3a58cca4798ebfc2edd673211067453e81e
commit: 5dd3a3a58cca4798ebfc2edd673211067453e81e
branch: main
author: Tomasz Pytel <tompy...@gmail.com>
committer: kumaraditya303 <kumaradi...@python.org>
date: 2025-05-08T18:51:36Z
summary:

gh-132551: make `io.BytesIO` thread safe (#132616)

Co-authored-by: Kumar Aditya <kumaradi...@python.org>

files:
A Lib/test/test_free_threading/test_io.py
A Misc/NEWS.d/next/Library/2025-04-16-21-02-57.gh-issue-132551.Psa7pL.rst
M Modules/_io/bytesio.c
M Modules/_io/clinic/bytesio.c.h

diff --git a/Lib/test/test_free_threading/test_io.py 
b/Lib/test/test_free_threading/test_io.py
new file mode 100644
index 00000000000000..f9bec740ddff50
--- /dev/null
+++ b/Lib/test/test_free_threading/test_io.py
@@ -0,0 +1,109 @@
+import threading
+from unittest import TestCase
+from test.support import threading_helper
+from random import randint
+from io import BytesIO
+from sys import getsizeof
+
+
+class TestBytesIO(TestCase):
+    # Test pretty much everything that can break under free-threading.
+    # Non-deterministic, but at least one of these things will fail if
+    # BytesIO object is not free-thread safe.
+
+    def check(self, funcs, *args):
+        barrier = threading.Barrier(len(funcs))
+        threads = []
+
+        for func in funcs:
+            thread = threading.Thread(target=func, args=(barrier, *args))
+
+            threads.append(thread)
+
+        with threading_helper.start_threads(threads):
+            pass
+
+    @threading_helper.requires_working_threading()
+    @threading_helper.reap_threads
+    def test_free_threading(self):
+        """Test for segfaults and aborts."""
+
+        def write(barrier, b, *ignore):
+            barrier.wait()
+            try: b.write(b'0' * randint(100, 1000))
+            except ValueError: pass  # ignore write fail to closed file
+
+        def writelines(barrier, b, *ignore):
+            barrier.wait()
+            b.write(b'0\n' * randint(100, 1000))
+
+        def truncate(barrier, b, *ignore):
+            barrier.wait()
+            try: b.truncate(0)
+            except: BufferError  # ignore exported buffer
+
+        def read(barrier, b, *ignore):
+            barrier.wait()
+            b.read()
+
+        def read1(barrier, b, *ignore):
+            barrier.wait()
+            b.read1()
+
+        def readline(barrier, b, *ignore):
+            barrier.wait()
+            b.readline()
+
+        def readlines(barrier, b, *ignore):
+            barrier.wait()
+            b.readlines()
+
+        def readinto(barrier, b, into, *ignore):
+            barrier.wait()
+            b.readinto(into)
+
+        def close(barrier, b, *ignore):
+            barrier.wait()
+            b.close()
+
+        def getvalue(barrier, b, *ignore):
+            barrier.wait()
+            b.getvalue()
+
+        def getbuffer(barrier, b, *ignore):
+            barrier.wait()
+            b.getbuffer()
+
+        def iter(barrier, b, *ignore):
+            barrier.wait()
+            list(b)
+
+        def getstate(barrier, b, *ignore):
+            barrier.wait()
+            b.__getstate__()
+
+        def setstate(barrier, b, st, *ignore):
+            barrier.wait()
+            b.__setstate__(st)
+
+        def sizeof(barrier, b, *ignore):
+            barrier.wait()
+            getsizeof(b)
+
+        self.check([write] * 10, BytesIO())
+        self.check([writelines] * 10, BytesIO())
+        self.check([write] * 10 + [truncate] * 10, BytesIO())
+        self.check([truncate] + [read] * 10, BytesIO(b'0\n'*204800))
+        self.check([truncate] + [read1] * 10, BytesIO(b'0\n'*204800))
+        self.check([truncate] + [readline] * 10, BytesIO(b'0\n'*20480))
+        self.check([truncate] + [readlines] * 10, BytesIO(b'0\n'*20480))
+        self.check([truncate] + [readinto] * 10, BytesIO(b'0\n'*204800), 
bytearray(b'0\n'*204800))
+        self.check([close] + [write] * 10, BytesIO())
+        self.check([truncate] + [getvalue] * 10, BytesIO(b'0\n'*204800))
+        self.check([truncate] + [getbuffer] * 10, BytesIO(b'0\n'*204800))
+        self.check([truncate] + [iter] * 10, BytesIO(b'0\n'*20480))
+        self.check([truncate] + [getstate] * 10, BytesIO(b'0\n'*204800))
+        self.check([truncate] + [setstate] * 10, BytesIO(b'0\n'*204800), 
(b'123', 0, None))
+        self.check([truncate] + [sizeof] * 10, BytesIO(b'0\n'*204800))
+
+        # no tests for seek or tell because they don't break anything
diff --git 
a/Misc/NEWS.d/next/Library/2025-04-16-21-02-57.gh-issue-132551.Psa7pL.rst 
b/Misc/NEWS.d/next/Library/2025-04-16-21-02-57.gh-issue-132551.Psa7pL.rst
new file mode 100644
index 00000000000000..c8743d49ca01d2
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-04-16-21-02-57.gh-issue-132551.Psa7pL.rst
@@ -0,0 +1 @@
+Make :class:`io.BytesIO` safe in :term:`free-threaded <free threading>` build.
diff --git a/Modules/_io/bytesio.c b/Modules/_io/bytesio.c
index e45a2d1a16dcba..4cde5f87032a3f 100644
--- a/Modules/_io/bytesio.c
+++ b/Modules/_io/bytesio.c
@@ -1,8 +1,10 @@
 #include "Python.h"
+#include "pycore_critical_section.h"  // Py_BEGIN_CRITICAL_SECTION()
 #include "pycore_object.h"
-#include "pycore_sysmodule.h"     // _PySys_GetSizeOf()
+#include "pycore_pyatomic_ft_wrappers.h"
+#include "pycore_sysmodule.h"         // _PySys_GetSizeOf()
 
-#include <stddef.h>               // offsetof()
+#include <stddef.h>                   // offsetof()
 #include "_iomodule.h"
 
 /*[clinic input]
@@ -50,7 +52,7 @@ check_closed(bytesio *self)
 static int
 check_exports(bytesio *self)
 {
-    if (self->exports > 0) {
+    if (FT_ATOMIC_LOAD_SSIZE_RELAXED(self->exports) > 0) {
         PyErr_SetString(PyExc_BufferError,
                         "Existing exports of data: object cannot be re-sized");
         return 1;
@@ -68,15 +70,17 @@ check_exports(bytesio *self)
         return NULL; \
     }
 
-#define SHARED_BUF(self) (Py_REFCNT((self)->buf) > 1)
+#define SHARED_BUF(self) (!_PyObject_IsUniquelyReferenced((self)->buf))
 
 
 /* Internal routine to get a line from the buffer of a BytesIO
    object. Returns the length between the current position to the
    next newline character. */
 static Py_ssize_t
-scan_eol(bytesio *self, Py_ssize_t len)
+scan_eol_lock_held(bytesio *self, Py_ssize_t len)
 {
+    _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self);
+
     const char *start, *n;
     Py_ssize_t maxlen;
 
@@ -109,11 +113,13 @@ scan_eol(bytesio *self, Py_ssize_t len)
    The caller should ensure that the 'size' argument is non-negative and
    not lesser than self->string_size.  Returns 0 on success, -1 otherwise. */
 static int
-unshare_buffer(bytesio *self, size_t size)
+unshare_buffer_lock_held(bytesio *self, size_t size)
 {
+    _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self);
+
     PyObject *new_buf;
     assert(SHARED_BUF(self));
-    assert(self->exports == 0);
+    assert(FT_ATOMIC_LOAD_SSIZE_RELAXED(self->exports) == 0);
     assert(size >= (size_t)self->string_size);
     new_buf = PyBytes_FromStringAndSize(NULL, size);
     if (new_buf == NULL)
@@ -128,10 +134,12 @@ unshare_buffer(bytesio *self, size_t size)
    The caller should ensure that the 'size' argument is non-negative.  Returns
    0 on success, -1 otherwise. */
 static int
-resize_buffer(bytesio *self, size_t size)
+resize_buffer_lock_held(bytesio *self, size_t size)
 {
+    _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self);
+
     assert(self->buf != NULL);
-    assert(self->exports == 0);
+    assert(FT_ATOMIC_LOAD_SSIZE_RELAXED(self->exports) == 0);
 
     /* Here, unsigned types are used to avoid dealing with signed integer
        overflow, which is undefined in C. */
@@ -160,7 +168,7 @@ resize_buffer(bytesio *self, size_t size)
     }
 
     if (SHARED_BUF(self)) {
-        if (unshare_buffer(self, alloc) < 0)
+        if (unshare_buffer_lock_held(self, alloc) < 0)
             return -1;
     }
     else {
@@ -181,8 +189,10 @@ resize_buffer(bytesio *self, size_t size)
    Inlining is disabled because it's significantly decreases performance
    of writelines() in PGO build. */
 Py_NO_INLINE static Py_ssize_t
-write_bytes(bytesio *self, PyObject *b)
+write_bytes_lock_held(bytesio *self, PyObject *b)
 {
+    _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self);
+
     if (check_closed(self)) {
         return -1;
     }
@@ -202,13 +212,13 @@ write_bytes(bytesio *self, PyObject *b)
     assert(self->pos >= 0);
     size_t endpos = (size_t)self->pos + len;
     if (endpos > (size_t)PyBytes_GET_SIZE(self->buf)) {
-        if (resize_buffer(self, endpos) < 0) {
+        if (resize_buffer_lock_held(self, endpos) < 0) {
             len = -1;
             goto done;
         }
     }
     else if (SHARED_BUF(self)) {
-        if (unshare_buffer(self, Py_MAX(endpos, (size_t)self->string_size)) < 
0) {
+        if (unshare_buffer_lock_held(self, Py_MAX(endpos, 
(size_t)self->string_size)) < 0) {
             len = -1;
             goto done;
         }
@@ -245,13 +255,17 @@ write_bytes(bytesio *self, PyObject *b)
 static PyObject *
 bytesio_get_closed(PyObject *op, void *Py_UNUSED(closure))
 {
+    PyObject *ret;
     bytesio *self = bytesio_CAST(op);
+    Py_BEGIN_CRITICAL_SECTION(self);
     if (self->buf == NULL) {
-        Py_RETURN_TRUE;
+        ret = Py_True;
     }
     else {
-        Py_RETURN_FALSE;
+        ret = Py_False;
     }
+    Py_END_CRITICAL_SECTION();
+    return ret;
 }
 
 /*[clinic input]
@@ -311,6 +325,7 @@ _io_BytesIO_flush_impl(bytesio *self)
 }
 
 /*[clinic input]
+@critical_section
 _io.BytesIO.getbuffer
 
     cls: defining_class
@@ -321,7 +336,7 @@ Get a read-write view over the contents of the BytesIO 
object.
 
 static PyObject *
 _io_BytesIO_getbuffer_impl(bytesio *self, PyTypeObject *cls)
-/*[clinic end generated code: output=045091d7ce87fe4e input=0668fbb48f95dffa]*/
+/*[clinic end generated code: output=045091d7ce87fe4e input=8295764061be77fd]*/
 {
     _PyIO_State *state = get_io_state_by_cls(cls);
     PyTypeObject *type = state->PyBytesIOBuffer_Type;
@@ -340,6 +355,7 @@ _io_BytesIO_getbuffer_impl(bytesio *self, PyTypeObject *cls)
 }
 
 /*[clinic input]
+@critical_section
 _io.BytesIO.getvalue
 
 Retrieve the entire contents of the BytesIO object.
@@ -347,16 +363,16 @@ Retrieve the entire contents of the BytesIO object.
 
 static PyObject *
 _io_BytesIO_getvalue_impl(bytesio *self)
-/*[clinic end generated code: output=b3f6a3233c8fd628 input=4b403ac0af3973ed]*/
+/*[clinic end generated code: output=b3f6a3233c8fd628 input=c91bff398df0c352]*/
 {
     CHECK_CLOSED(self);
-    if (self->string_size <= 1 || self->exports > 0)
+    if (self->string_size <= 1 || FT_ATOMIC_LOAD_SSIZE_RELAXED(self->exports) 
> 0)
         return PyBytes_FromStringAndSize(PyBytes_AS_STRING(self->buf),
                                          self->string_size);
 
     if (self->string_size != PyBytes_GET_SIZE(self->buf)) {
         if (SHARED_BUF(self)) {
-            if (unshare_buffer(self, self->string_size) < 0)
+            if (unshare_buffer_lock_held(self, self->string_size) < 0)
                 return NULL;
         }
         else {
@@ -384,6 +400,7 @@ _io_BytesIO_isatty_impl(bytesio *self)
 }
 
 /*[clinic input]
+@critical_section
 _io.BytesIO.tell
 
 Current file position, an integer.
@@ -391,22 +408,24 @@ Current file position, an integer.
 
 static PyObject *
 _io_BytesIO_tell_impl(bytesio *self)
-/*[clinic end generated code: output=b54b0f93cd0e5e1d input=b106adf099cb3657]*/
+/*[clinic end generated code: output=b54b0f93cd0e5e1d input=2c7b0e8f82e05c4d]*/
 {
     CHECK_CLOSED(self);
     return PyLong_FromSsize_t(self->pos);
 }
 
 static PyObject *
-read_bytes(bytesio *self, Py_ssize_t size)
+read_bytes_lock_held(bytesio *self, Py_ssize_t size)
 {
+    _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self);
+
     const char *output;
 
     assert(self->buf != NULL);
     assert(size <= self->string_size);
     if (size > 1 &&
         self->pos == 0 && size == PyBytes_GET_SIZE(self->buf) &&
-        self->exports == 0) {
+        FT_ATOMIC_LOAD_SSIZE_RELAXED(self->exports) == 0) {
         self->pos += size;
         return Py_NewRef(self->buf);
     }
@@ -417,6 +436,7 @@ read_bytes(bytesio *self, Py_ssize_t size)
 }
 
 /*[clinic input]
+@critical_section
 _io.BytesIO.read
     size: Py_ssize_t(accept={int, NoneType}) = -1
     /
@@ -429,7 +449,7 @@ Return an empty bytes object at EOF.
 
 static PyObject *
 _io_BytesIO_read_impl(bytesio *self, Py_ssize_t size)
-/*[clinic end generated code: output=9cc025f21c75bdd2 input=74344a39f431c3d7]*/
+/*[clinic end generated code: output=9cc025f21c75bdd2 input=9e2f7ff3075fdd39]*/
 {
     Py_ssize_t n;
 
@@ -443,11 +463,12 @@ _io_BytesIO_read_impl(bytesio *self, Py_ssize_t size)
             size = 0;
     }
 
-    return read_bytes(self, size);
+    return read_bytes_lock_held(self, size);
 }
 
 
 /*[clinic input]
+@critical_section
 _io.BytesIO.read1
     size: Py_ssize_t(accept={int, NoneType}) = -1
     /
@@ -460,12 +481,13 @@ Return an empty bytes object at EOF.
 
 static PyObject *
 _io_BytesIO_read1_impl(bytesio *self, Py_ssize_t size)
-/*[clinic end generated code: output=d0f843285aa95f1c input=440a395bf9129ef5]*/
+/*[clinic end generated code: output=d0f843285aa95f1c input=a08fc9e507ab380c]*/
 {
     return _io_BytesIO_read_impl(self, size);
 }
 
 /*[clinic input]
+@critical_section
 _io.BytesIO.readline
     size: Py_ssize_t(accept={int, NoneType}) = -1
     /
@@ -479,18 +501,19 @@ Return an empty bytes object at EOF.
 
 static PyObject *
 _io_BytesIO_readline_impl(bytesio *self, Py_ssize_t size)
-/*[clinic end generated code: output=4bff3c251df8ffcd input=e7c3fbd1744e2783]*/
+/*[clinic end generated code: output=4bff3c251df8ffcd input=db09d47e23cf2c9e]*/
 {
     Py_ssize_t n;
 
     CHECK_CLOSED(self);
 
-    n = scan_eol(self, size);
+    n = scan_eol_lock_held(self, size);
 
-    return read_bytes(self, n);
+    return read_bytes_lock_held(self, n);
 }
 
 /*[clinic input]
+@critical_section
 _io.BytesIO.readlines
     size as arg: object = None
     /
@@ -504,7 +527,7 @@ total number of bytes in the lines returned.
 
 static PyObject *
 _io_BytesIO_readlines_impl(bytesio *self, PyObject *arg)
-/*[clinic end generated code: output=09b8e34c880808ff input=691aa1314f2c2a87]*/
+/*[clinic end generated code: output=09b8e34c880808ff input=5c57d7d78e409985]*/
 {
     Py_ssize_t maxsize, size, n;
     PyObject *result, *line;
@@ -533,7 +556,7 @@ _io_BytesIO_readlines_impl(bytesio *self, PyObject *arg)
         return NULL;
 
     output = PyBytes_AS_STRING(self->buf) + self->pos;
-    while ((n = scan_eol(self, -1)) != 0) {
+    while ((n = scan_eol_lock_held(self, -1)) != 0) {
         self->pos += n;
         line = PyBytes_FromStringAndSize(output, n);
         if (!line)
@@ -556,6 +579,7 @@ _io_BytesIO_readlines_impl(bytesio *self, PyObject *arg)
 }
 
 /*[clinic input]
+@critical_section
 _io.BytesIO.readinto
     buffer: Py_buffer(accept={rwbuffer})
     /
@@ -568,7 +592,7 @@ is set not to block and has no data to read.
 
 static PyObject *
 _io_BytesIO_readinto_impl(bytesio *self, Py_buffer *buffer)
-/*[clinic end generated code: output=a5d407217dcf0639 input=1424d0fdce857919]*/
+/*[clinic end generated code: output=a5d407217dcf0639 input=093a8d330de3fcd1]*/
 {
     Py_ssize_t len, n;
 
@@ -592,8 +616,9 @@ _io_BytesIO_readinto_impl(bytesio *self, Py_buffer *buffer)
 }
 
 /*[clinic input]
+@critical_section
 _io.BytesIO.truncate
-    size: Py_ssize_t(accept={int, NoneType}, c_default="((bytesio 
*)self)->pos") = None
+    size: object = None
     /
 
 Truncate the file to at most size bytes.
@@ -603,44 +628,68 @@ The current file position is unchanged.  Returns the new 
size.
 [clinic start generated code]*/
 
 static PyObject *
-_io_BytesIO_truncate_impl(bytesio *self, Py_ssize_t size)
-/*[clinic end generated code: output=9ad17650c15fa09b input=dae4295e11c1bbb4]*/
+_io_BytesIO_truncate_impl(bytesio *self, PyObject *size)
+/*[clinic end generated code: output=ab42491b4824f384 input=b4acb5f80481c053]*/
 {
     CHECK_CLOSED(self);
     CHECK_EXPORTS(self);
 
-    if (size < 0) {
-        PyErr_Format(PyExc_ValueError,
-                     "negative size value %zd", size);
-        return NULL;
+    Py_ssize_t new_size;
+
+    if (size == Py_None) {
+        new_size = self->pos;
+    }
+    else {
+        new_size = PyLong_AsLong(size);
+        if (new_size == -1 && PyErr_Occurred()) {
+            return NULL;
+        }
+        if (new_size < 0) {
+            PyErr_Format(PyExc_ValueError,
+                         "negative size value %zd", new_size);
+            return NULL;
+        }
     }
 
-    if (size < self->string_size) {
-        self->string_size = size;
-        if (resize_buffer(self, size) < 0)
+    if (new_size < self->string_size) {
+        self->string_size = new_size;
+        if (resize_buffer_lock_held(self, new_size) < 0)
             return NULL;
     }
 
-    return PyLong_FromSsize_t(size);
+    return PyLong_FromSsize_t(new_size);
 }
 
 static PyObject *
-bytesio_iternext(PyObject *op)
+bytesio_iternext_lock_held(PyObject *op)
 {
+    _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op);
+
     Py_ssize_t n;
     bytesio *self = bytesio_CAST(op);
 
     CHECK_CLOSED(self);
 
-    n = scan_eol(self, -1);
+    n = scan_eol_lock_held(self, -1);
 
     if (n == 0)
         return NULL;
 
-    return read_bytes(self, n);
+    return read_bytes_lock_held(self, n);
+}
+
+static PyObject *
+bytesio_iternext(PyObject *op)
+{
+    PyObject *ret;
+    Py_BEGIN_CRITICAL_SECTION(op);
+    ret = bytesio_iternext_lock_held(op);
+    Py_END_CRITICAL_SECTION();
+    return ret;
 }
 
 /*[clinic input]
+@critical_section
 _io.BytesIO.seek
     pos: Py_ssize_t
     whence: int = 0
@@ -657,7 +706,7 @@ Returns the new absolute position.
 
 static PyObject *
 _io_BytesIO_seek_impl(bytesio *self, Py_ssize_t pos, int whence)
-/*[clinic end generated code: output=c26204a68e9190e4 input=1e875e6ebc652948]*/
+/*[clinic end generated code: output=c26204a68e9190e4 input=20f05ddf659255df]*/
 {
     CHECK_CLOSED(self);
 
@@ -700,6 +749,7 @@ _io_BytesIO_seek_impl(bytesio *self, Py_ssize_t pos, int 
whence)
 }
 
 /*[clinic input]
+@critical_section
 _io.BytesIO.write
     b: object
     /
@@ -711,13 +761,14 @@ Return the number of bytes written.
 
 static PyObject *
 _io_BytesIO_write_impl(bytesio *self, PyObject *b)
-/*[clinic end generated code: output=d3e46bcec8d9e21c input=f5ec7c8c64ed720a]*/
+/*[clinic end generated code: output=d3e46bcec8d9e21c input=46c0c17eac7474a4]*/
 {
-    Py_ssize_t n = write_bytes(self, b);
+    Py_ssize_t n = write_bytes_lock_held(self, b);
     return n >= 0 ? PyLong_FromSsize_t(n) : NULL;
 }
 
 /*[clinic input]
+@critical_section
 _io.BytesIO.writelines
     lines: object
     /
@@ -731,7 +782,7 @@ each element.
 
 static PyObject *
 _io_BytesIO_writelines_impl(bytesio *self, PyObject *lines)
-/*[clinic end generated code: output=03a43a75773bc397 input=e972539176fc8fc1]*/
+/*[clinic end generated code: output=03a43a75773bc397 input=5d6a616ae39dc9ca]*/
 {
     PyObject *it, *item;
 
@@ -742,7 +793,7 @@ _io_BytesIO_writelines_impl(bytesio *self, PyObject *lines)
         return NULL;
 
     while ((item = PyIter_Next(it)) != NULL) {
-        Py_ssize_t ret = write_bytes(self, item);
+        Py_ssize_t ret = write_bytes_lock_held(self, item);
         Py_DECREF(item);
         if (ret < 0) {
             Py_DECREF(it);
@@ -759,6 +810,7 @@ _io_BytesIO_writelines_impl(bytesio *self, PyObject *lines)
 }
 
 /*[clinic input]
+@critical_section
 _io.BytesIO.close
 
 Disable all I/O operations.
@@ -766,7 +818,7 @@ Disable all I/O operations.
 
 static PyObject *
 _io_BytesIO_close_impl(bytesio *self)
-/*[clinic end generated code: output=1471bb9411af84a0 input=37e1f55556e61f60]*/
+/*[clinic end generated code: output=1471bb9411af84a0 input=34ce76d8bd17a23b]*/
 {
     CHECK_EXPORTS(self);
     Py_CLEAR(self->buf);
@@ -788,35 +840,49 @@ _io_BytesIO_close_impl(bytesio *self)
    function to use the efficient instance representation of PEP 307.
  */
 
+ static PyObject *
+ bytesio_getstate_lock_held(PyObject *op)
+ {
+     _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op);
+
+     bytesio *self = bytesio_CAST(op);
+     PyObject *initvalue = _io_BytesIO_getvalue_impl(self);
+     PyObject *dict;
+     PyObject *state;
+
+     if (initvalue == NULL)
+         return NULL;
+     if (self->dict == NULL) {
+         dict = Py_NewRef(Py_None);
+     }
+     else {
+         dict = PyDict_Copy(self->dict);
+         if (dict == NULL) {
+             Py_DECREF(initvalue);
+             return NULL;
+         }
+     }
+
+     state = Py_BuildValue("(OnN)", initvalue, self->pos, dict);
+     Py_DECREF(initvalue);
+     return state;
+}
+
 static PyObject *
 bytesio_getstate(PyObject *op, PyObject *Py_UNUSED(dummy))
 {
-    bytesio *self = bytesio_CAST(op);
-    PyObject *initvalue = _io_BytesIO_getvalue_impl(self);
-    PyObject *dict;
-    PyObject *state;
-
-    if (initvalue == NULL)
-        return NULL;
-    if (self->dict == NULL) {
-        dict = Py_NewRef(Py_None);
-    }
-    else {
-        dict = PyDict_Copy(self->dict);
-        if (dict == NULL) {
-            Py_DECREF(initvalue);
-            return NULL;
-        }
-    }
-
-    state = Py_BuildValue("(OnN)", initvalue, self->pos, dict);
-    Py_DECREF(initvalue);
-    return state;
+    PyObject *ret;
+    Py_BEGIN_CRITICAL_SECTION(op);
+    ret = bytesio_getstate_lock_held(op);
+    Py_END_CRITICAL_SECTION();
+    return ret;
 }
 
 static PyObject *
-bytesio_setstate(PyObject *op, PyObject *state)
+bytesio_setstate_lock_held(PyObject *op, PyObject *state)
 {
+    _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op);
+
     PyObject *result;
     PyObject *position_obj;
     PyObject *dict;
@@ -890,13 +956,23 @@ bytesio_setstate(PyObject *op, PyObject *state)
     Py_RETURN_NONE;
 }
 
+static PyObject *
+bytesio_setstate(PyObject *op, PyObject *state)
+{
+    PyObject *ret;
+    Py_BEGIN_CRITICAL_SECTION(op);
+    ret = bytesio_setstate_lock_held(op, state);
+    Py_END_CRITICAL_SECTION();
+    return ret;
+}
+
 static void
 bytesio_dealloc(PyObject *op)
 {
     bytesio *self = bytesio_CAST(op);
     PyTypeObject *tp = Py_TYPE(self);
     _PyObject_GC_UNTRACK(self);
-    if (self->exports > 0) {
+    if (FT_ATOMIC_LOAD_SSIZE_RELAXED(self->exports) > 0) {
         PyErr_SetString(PyExc_SystemError,
                         "deallocated BytesIO object has exported buffers");
         PyErr_Print();
@@ -932,6 +1008,7 @@ bytesio_new(PyTypeObject *type, PyObject *args, PyObject 
*kwds)
 }
 
 /*[clinic input]
+@critical_section
 _io.BytesIO.__init__
     initial_bytes as initvalue: object(c_default="NULL") = b''
 
@@ -940,13 +1017,13 @@ Buffered I/O implementation using an in-memory bytes 
buffer.
 
 static int
 _io_BytesIO___init___impl(bytesio *self, PyObject *initvalue)
-/*[clinic end generated code: output=65c0c51e24c5b621 input=aac7f31b67bf0fb6]*/
+/*[clinic end generated code: output=65c0c51e24c5b621 input=3da5a74ee4c4f1ac]*/
 {
     /* In case, __init__ is called multiple times. */
     self->string_size = 0;
     self->pos = 0;
 
-    if (self->exports > 0) {
+    if (FT_ATOMIC_LOAD_SSIZE_RELAXED(self->exports) > 0) {
         PyErr_SetString(PyExc_BufferError,
                         "Existing exports of data: object cannot be re-sized");
         return -1;
@@ -970,8 +1047,10 @@ _io_BytesIO___init___impl(bytesio *self, PyObject 
*initvalue)
 }
 
 static PyObject *
-bytesio_sizeof(PyObject *op, PyObject *Py_UNUSED(dummy))
+bytesio_sizeof_lock_held(PyObject *op)
 {
+    _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op);
+
     bytesio *self = bytesio_CAST(op);
     size_t res = _PyObject_SIZE(Py_TYPE(self));
     if (self->buf && !SHARED_BUF(self)) {
@@ -984,6 +1063,16 @@ bytesio_sizeof(PyObject *op, PyObject *Py_UNUSED(dummy))
     return PyLong_FromSize_t(res);
 }
 
+static PyObject *
+bytesio_sizeof(PyObject *op, PyObject *Py_UNUSED(dummy))
+{
+    PyObject *ret;
+    Py_BEGIN_CRITICAL_SECTION(op);
+    ret = bytesio_sizeof_lock_held(op);
+    Py_END_CRITICAL_SECTION();
+    return ret;
+}
+
 static int
 bytesio_traverse(PyObject *op, visitproc visit, void *arg)
 {
@@ -999,7 +1088,7 @@ bytesio_clear(PyObject *op)
 {
     bytesio *self = bytesio_CAST(op);
     Py_CLEAR(self->dict);
-    if (self->exports == 0) {
+    if (FT_ATOMIC_LOAD_SSIZE_RELAXED(self->exports) == 0) {
         Py_CLEAR(self->buf);
     }
     return 0;
@@ -1077,18 +1166,15 @@ PyType_Spec bytesio_spec = {
  */
 
 static int
-bytesiobuf_getbuffer(PyObject *op, Py_buffer *view, int flags)
+bytesiobuf_getbuffer_lock_held(PyObject *op, Py_buffer *view, int flags)
 {
     bytesiobuf *obj = bytesiobuf_CAST(op);
     bytesio *b = bytesio_CAST(obj->source);
 
-    if (view == NULL) {
-        PyErr_SetString(PyExc_BufferError,
-            "bytesiobuf_getbuffer: view==NULL argument is obsolete");
-        return -1;
-    }
-    if (b->exports == 0 && SHARED_BUF(b)) {
-        if (unshare_buffer(b, b->string_size) < 0)
+    _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(b);
+
+    if (FT_ATOMIC_LOAD_SSIZE_RELAXED(b->exports) == 0 && SHARED_BUF(b)) {
+        if (unshare_buffer_lock_held(b, b->string_size) < 0)
             return -1;
     }
 
@@ -1096,16 +1182,32 @@ bytesiobuf_getbuffer(PyObject *op, Py_buffer *view, int 
flags)
     (void)PyBuffer_FillInfo(view, op,
                             PyBytes_AS_STRING(b->buf), b->string_size,
                             0, flags);
-    b->exports++;
+    FT_ATOMIC_ADD_SSIZE(b->exports, 1);
     return 0;
 }
 
+static int
+bytesiobuf_getbuffer(PyObject *op, Py_buffer *view, int flags)
+{
+    if (view == NULL) {
+        PyErr_SetString(PyExc_BufferError,
+            "bytesiobuf_getbuffer: view==NULL argument is obsolete");
+        return -1;
+    }
+
+    int ret;
+    Py_BEGIN_CRITICAL_SECTION(bytesiobuf_CAST(op)->source);
+    ret = bytesiobuf_getbuffer_lock_held(op, view, flags);
+    Py_END_CRITICAL_SECTION();
+    return ret;
+}
+
 static void
 bytesiobuf_releasebuffer(PyObject *op, Py_buffer *Py_UNUSED(view))
 {
     bytesiobuf *obj = bytesiobuf_CAST(op);
     bytesio *b = bytesio_CAST(obj->source);
-    b->exports--;
+    FT_ATOMIC_ADD_SSIZE(b->exports, -1);
 }
 
 static int
diff --git a/Modules/_io/clinic/bytesio.c.h b/Modules/_io/clinic/bytesio.c.h
index aaf4884d1732a9..8553ed05f70d1b 100644
--- a/Modules/_io/clinic/bytesio.c.h
+++ b/Modules/_io/clinic/bytesio.c.h
@@ -7,6 +7,7 @@ preserve
 #  include "pycore_runtime.h"     // _Py_ID()
 #endif
 #include "pycore_abstract.h"      // _Py_convert_optional_to_ssize_t()
+#include "pycore_critical_section.h"// Py_BEGIN_CRITICAL_SECTION()
 #include "pycore_modsupport.h"    // _PyArg_CheckPositional()
 
 PyDoc_STRVAR(_io_BytesIO_readable__doc__,
@@ -96,11 +97,18 @@ _io_BytesIO_getbuffer_impl(bytesio *self, PyTypeObject 
*cls);
 static PyObject *
 _io_BytesIO_getbuffer(PyObject *self, PyTypeObject *cls, PyObject *const 
*args, Py_ssize_t nargs, PyObject *kwnames)
 {
+    PyObject *return_value = NULL;
+
     if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) {
         PyErr_SetString(PyExc_TypeError, "getbuffer() takes no arguments");
-        return NULL;
+        goto exit;
     }
-    return _io_BytesIO_getbuffer_impl((bytesio *)self, cls);
+    Py_BEGIN_CRITICAL_SECTION(self);
+    return_value = _io_BytesIO_getbuffer_impl((bytesio *)self, cls);
+    Py_END_CRITICAL_SECTION();
+
+exit:
+    return return_value;
 }
 
 PyDoc_STRVAR(_io_BytesIO_getvalue__doc__,
@@ -118,7 +126,13 @@ _io_BytesIO_getvalue_impl(bytesio *self);
 static PyObject *
 _io_BytesIO_getvalue(PyObject *self, PyObject *Py_UNUSED(ignored))
 {
-    return _io_BytesIO_getvalue_impl((bytesio *)self);
+    PyObject *return_value = NULL;
+
+    Py_BEGIN_CRITICAL_SECTION(self);
+    return_value = _io_BytesIO_getvalue_impl((bytesio *)self);
+    Py_END_CRITICAL_SECTION();
+
+    return return_value;
 }
 
 PyDoc_STRVAR(_io_BytesIO_isatty__doc__,
@@ -156,7 +170,13 @@ _io_BytesIO_tell_impl(bytesio *self);
 static PyObject *
 _io_BytesIO_tell(PyObject *self, PyObject *Py_UNUSED(ignored))
 {
-    return _io_BytesIO_tell_impl((bytesio *)self);
+    PyObject *return_value = NULL;
+
+    Py_BEGIN_CRITICAL_SECTION(self);
+    return_value = _io_BytesIO_tell_impl((bytesio *)self);
+    Py_END_CRITICAL_SECTION();
+
+    return return_value;
 }
 
 PyDoc_STRVAR(_io_BytesIO_read__doc__,
@@ -190,7 +210,9 @@ _io_BytesIO_read(PyObject *self, PyObject *const *args, 
Py_ssize_t nargs)
         goto exit;
     }
 skip_optional:
+    Py_BEGIN_CRITICAL_SECTION(self);
     return_value = _io_BytesIO_read_impl((bytesio *)self, size);
+    Py_END_CRITICAL_SECTION();
 
 exit:
     return return_value;
@@ -227,7 +249,9 @@ _io_BytesIO_read1(PyObject *self, PyObject *const *args, 
Py_ssize_t nargs)
         goto exit;
     }
 skip_optional:
+    Py_BEGIN_CRITICAL_SECTION(self);
     return_value = _io_BytesIO_read1_impl((bytesio *)self, size);
+    Py_END_CRITICAL_SECTION();
 
 exit:
     return return_value;
@@ -265,7 +289,9 @@ _io_BytesIO_readline(PyObject *self, PyObject *const *args, 
Py_ssize_t nargs)
         goto exit;
     }
 skip_optional:
+    Py_BEGIN_CRITICAL_SECTION(self);
     return_value = _io_BytesIO_readline_impl((bytesio *)self, size);
+    Py_END_CRITICAL_SECTION();
 
 exit:
     return return_value;
@@ -301,7 +327,9 @@ _io_BytesIO_readlines(PyObject *self, PyObject *const 
*args, Py_ssize_t nargs)
     }
     arg = args[0];
 skip_optional:
+    Py_BEGIN_CRITICAL_SECTION(self);
     return_value = _io_BytesIO_readlines_impl((bytesio *)self, arg);
+    Py_END_CRITICAL_SECTION();
 
 exit:
     return return_value;
@@ -332,7 +360,9 @@ _io_BytesIO_readinto(PyObject *self, PyObject *arg)
         _PyArg_BadArgument("readinto", "argument", "read-write bytes-like 
object", arg);
         goto exit;
     }
+    Py_BEGIN_CRITICAL_SECTION(self);
     return_value = _io_BytesIO_readinto_impl((bytesio *)self, &buffer);
+    Py_END_CRITICAL_SECTION();
 
 exit:
     /* Cleanup for buffer */
@@ -356,13 +386,13 @@ PyDoc_STRVAR(_io_BytesIO_truncate__doc__,
     {"truncate", _PyCFunction_CAST(_io_BytesIO_truncate), METH_FASTCALL, 
_io_BytesIO_truncate__doc__},
 
 static PyObject *
-_io_BytesIO_truncate_impl(bytesio *self, Py_ssize_t size);
+_io_BytesIO_truncate_impl(bytesio *self, PyObject *size);
 
 static PyObject *
 _io_BytesIO_truncate(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
 {
     PyObject *return_value = NULL;
-    Py_ssize_t size = ((bytesio *)self)->pos;
+    PyObject *size = Py_None;
 
     if (!_PyArg_CheckPositional("truncate", nargs, 0, 1)) {
         goto exit;
@@ -370,11 +400,11 @@ _io_BytesIO_truncate(PyObject *self, PyObject *const 
*args, Py_ssize_t nargs)
     if (nargs < 1) {
         goto skip_optional;
     }
-    if (!_Py_convert_optional_to_ssize_t(args[0], &size)) {
-        goto exit;
-    }
+    size = args[0];
 skip_optional:
+    Py_BEGIN_CRITICAL_SECTION(self);
     return_value = _io_BytesIO_truncate_impl((bytesio *)self, size);
+    Py_END_CRITICAL_SECTION();
 
 exit:
     return return_value;
@@ -428,7 +458,9 @@ _io_BytesIO_seek(PyObject *self, PyObject *const *args, 
Py_ssize_t nargs)
         goto exit;
     }
 skip_optional:
+    Py_BEGIN_CRITICAL_SECTION(self);
     return_value = _io_BytesIO_seek_impl((bytesio *)self, pos, whence);
+    Py_END_CRITICAL_SECTION();
 
 exit:
     return return_value;
@@ -453,7 +485,9 @@ _io_BytesIO_write(PyObject *self, PyObject *b)
 {
     PyObject *return_value = NULL;
 
+    Py_BEGIN_CRITICAL_SECTION(self);
     return_value = _io_BytesIO_write_impl((bytesio *)self, b);
+    Py_END_CRITICAL_SECTION();
 
     return return_value;
 }
@@ -479,7 +513,9 @@ _io_BytesIO_writelines(PyObject *self, PyObject *lines)
 {
     PyObject *return_value = NULL;
 
+    Py_BEGIN_CRITICAL_SECTION(self);
     return_value = _io_BytesIO_writelines_impl((bytesio *)self, lines);
+    Py_END_CRITICAL_SECTION();
 
     return return_value;
 }
@@ -499,7 +535,13 @@ _io_BytesIO_close_impl(bytesio *self);
 static PyObject *
 _io_BytesIO_close(PyObject *self, PyObject *Py_UNUSED(ignored))
 {
-    return _io_BytesIO_close_impl((bytesio *)self);
+    PyObject *return_value = NULL;
+
+    Py_BEGIN_CRITICAL_SECTION(self);
+    return_value = _io_BytesIO_close_impl((bytesio *)self);
+    Py_END_CRITICAL_SECTION();
+
+    return return_value;
 }
 
 PyDoc_STRVAR(_io_BytesIO___init____doc__,
@@ -558,9 +600,11 @@ _io_BytesIO___init__(PyObject *self, PyObject *args, 
PyObject *kwargs)
     }
     initvalue = fastargs[0];
 skip_optional_pos:
+    Py_BEGIN_CRITICAL_SECTION(self);
     return_value = _io_BytesIO___init___impl((bytesio *)self, initvalue);
+    Py_END_CRITICAL_SECTION();
 
 exit:
     return return_value;
 }
-/*[clinic end generated code: output=6dbfd82f4e9d4ef3 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=580205daa01def2e input=a9049054013a1b77]*/

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to