class CBytesIOTest(ThreadSafetyMixin, TestCase):
ioclass = io.BytesIO
+ @threading_helper.requires_working_threading()
+ @threading_helper.reap_threads
+ def test_concurrent_whole_buffer_read_and_resize(self):
+ shared = self.ioclass(b"x" * 64)
+ writers = 2
+ readers = 8
+ loops = 2000
+ barrier = threading.Barrier(writers + readers)
+
+ def writer():
+ barrier.wait()
+ for i in range(loops):
+ shared.seek(0)
+ shared.write(b"a" * (64 + (i & 63)))
+
+ def reader():
+ barrier.wait()
+ for _ in range(loops):
+ shared.seek(0)
+ shared.read()
+ shared.seek(0)
+ shared.getvalue()
+
+ threads = [threading.Thread(target=writer) for _ in range(writers)]
+ threads += [threading.Thread(target=reader) for _ in range(readers)]
+ with threading_helper.start_threads(threads):
+ pass
+
class PyBytesIOTest(ThreadSafetyMixin, TestCase):
ioclass = pyio.BytesIO
@support.cpython_only
def test_sizeof(self):
- basesize = support.calcobjsize('P2n2Pn')
+ if support.Py_GIL_DISABLED:
+ basesize = support.calcobjsize('P2n2Pni')
+ else:
+ basesize = support.calcobjsize('P2n2Pn')
check = self.check_sizeof
self.assertEqual(object.__sizeof__(io.BytesIO()), basesize)
check(io.BytesIO(), basesize )
PyObject *dict;
PyObject *weakreflist;
Py_ssize_t exports;
+#ifdef Py_GIL_DISABLED
+ int buf_shared;
+#endif
} bytesio;
#define bytesio_CAST(op) ((bytesio *)(op))
return NULL; \
}
+#ifdef Py_GIL_DISABLED
+#define SHARED_BUF(self) ((self)->buf_shared || !_PyObject_IsUniquelyReferenced((self)->buf))
+#else
#define SHARED_BUF(self) (!_PyObject_IsUniquelyReferenced((self)->buf))
+#endif
+
+static inline void
+set_shared_buf(bytesio *self)
+{
+#ifdef Py_GIL_DISABLED
+ self->buf_shared = 1;
+#endif
+}
+
+static inline void
+clear_shared_buf(bytesio *self)
+{
+#ifdef Py_GIL_DISABLED
+ self->buf_shared = 0;
+#endif
+}
+
+static int
+resize_unshared_buffer_lock_held(bytesio *self, Py_ssize_t size)
+{
+ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self);
+
+#ifdef Py_GIL_DISABLED
+ /* If the internal bytes object escaped via a zero-copy getvalue(), read(),
+ or peek(), resizing it would mutate an object visible to Python code.
+ Callers must detach first. */
+ assert(!self->buf_shared);
+#endif
+ int ret = _PyBytes_Resize(&self->buf, size);
+ if (ret == 0) {
+ clear_shared_buf(self);
+ }
+ return ret;
+}
/* Internal routine to get a line from the buffer of a BytesIO
memcpy(PyBytes_AS_STRING(new_buf), PyBytes_AS_STRING(self->buf),
self->string_size);
Py_SETREF(self->buf, new_buf);
+ clear_shared_buf(self);
return 0;
}
return -1;
}
else {
- if (_PyBytes_Resize(&self->buf, alloc) < 0)
+ if (resize_unshared_buffer_lock_held(self, alloc) < 0)
return -1;
}
return NULL;
}
else {
- if (_PyBytes_Resize(&self->buf, self->string_size) < 0)
+ if (resize_unshared_buffer_lock_held(self, self->string_size) < 0)
return NULL;
}
}
+ set_shared_buf(self);
return Py_NewRef(self->buf);
}
self->pos == 0 && size == PyBytes_GET_SIZE(self->buf) &&
FT_ATOMIC_LOAD_SSIZE_RELAXED(self->exports) == 0) {
self->pos += size;
+ set_shared_buf(self);
return Py_NewRef(self->buf);
}
if (initvalue && initvalue != Py_None) {
if (PyBytes_CheckExact(initvalue)) {
Py_XSETREF(self->buf, Py_NewRef(initvalue));
+ clear_shared_buf(self);
self->string_size = PyBytes_GET_SIZE(initvalue);
}
else {