Skip to content

Commit f70aef9

Browse files
committed
gh-151640: avoid sharing BytesIO buffer in free-threaded builds
1 parent 93454fe commit f70aef9

2 files changed

Lines changed: 36 additions & 0 deletions

File tree

Lib/test/test_free_threading/test_io.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,35 @@ def sizeof(barrier, b, *ignore):
122122
class CBytesIOTest(ThreadSafetyMixin, TestCase):
123123
ioclass = io.BytesIO
124124

125+
@threading_helper.requires_working_threading()
126+
@threading_helper.reap_threads
127+
def test_concurrent_whole_buffer_read_and_resize(self):
128+
shared = self.ioclass(b"x" * 64)
129+
writers = 2
130+
readers = 8
131+
loops = 2000
132+
barrier = threading.Barrier(writers + readers)
133+
134+
def writer():
135+
barrier.wait()
136+
for i in range(loops):
137+
shared.seek(0)
138+
shared.write(b"a" * (64 + (i & 63)))
139+
140+
def reader():
141+
barrier.wait()
142+
for _ in range(loops):
143+
shared.seek(0)
144+
shared.read()
145+
shared.seek(0)
146+
shared.peek()
147+
shared.getvalue()
148+
149+
threads = [threading.Thread(target=writer) for _ in range(writers)]
150+
threads += [threading.Thread(target=reader) for _ in range(readers)]
151+
with threading_helper.start_threads(threads):
152+
pass
153+
125154
class PyBytesIOTest(ThreadSafetyMixin, TestCase):
126155
ioclass = pyio.BytesIO
127156

Modules/_io/bytesio.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,10 @@ _io_BytesIO_getvalue_impl(bytesio *self)
371371
/*[clinic end generated code: output=b3f6a3233c8fd628 input=c91bff398df0c352]*/
372372
{
373373
CHECK_CLOSED(self);
374+
#ifdef Py_GIL_DISABLED
375+
return PyBytes_FromStringAndSize(PyBytes_AS_STRING(self->buf),
376+
self->string_size);
377+
#else
374378
if (self->string_size <= 1 || FT_ATOMIC_LOAD_SSIZE_RELAXED(self->exports) > 0)
375379
return PyBytes_FromStringAndSize(PyBytes_AS_STRING(self->buf),
376380
self->string_size);
@@ -386,6 +390,7 @@ _io_BytesIO_getvalue_impl(bytesio *self)
386390
}
387391
}
388392
return Py_NewRef(self->buf);
393+
#endif
389394
}
390395

391396
/*[clinic input]
@@ -430,11 +435,13 @@ peek_bytes_lock_held(bytesio *self, Py_ssize_t size)
430435

431436
assert(self->buf != NULL);
432437
assert(size <= self->string_size);
438+
#ifndef Py_GIL_DISABLED
433439
if (size > 1 &&
434440
self->pos == 0 && size == PyBytes_GET_SIZE(self->buf) &&
435441
FT_ATOMIC_LOAD_SSIZE_RELAXED(self->exports) == 0) {
436442
return Py_NewRef(self->buf);
437443
}
444+
#endif
438445

439446
/* gh-141311: Avoid undefined behavior when self->pos (limit PY_SSIZE_T_MAX)
440447
is beyond the size of self->buf. Assert above validates size is always in

0 commit comments

Comments
 (0)