mirror of
https://github.com/python/cpython.git
synced 2025-07-07 19:35:27 +00:00
gh-134908: Protect textiowrapper_iternext
with critical section (gh-134910)
The `textiowrapper_iternext` function called `_textiowrapper_writeflush`, but did not use a critical section, making it racy in free-threaded builds.
This commit is contained in:
parent
055827528f
commit
44fb7c361c
3 changed files with 46 additions and 1 deletions
|
@ -1062,6 +1062,37 @@ class IOTest(unittest.TestCase):
|
|||
# Silence destructor error
|
||||
R.flush = lambda self: None
|
||||
|
||||
@threading_helper.requires_working_threading()
|
||||
def test_write_readline_races(self):
|
||||
# gh-134908: Concurrent iteration over a file caused races
|
||||
thread_count = 2
|
||||
write_count = 100
|
||||
read_count = 100
|
||||
|
||||
def writer(file, barrier):
|
||||
barrier.wait()
|
||||
for _ in range(write_count):
|
||||
file.write("x")
|
||||
|
||||
def reader(file, barrier):
|
||||
barrier.wait()
|
||||
for _ in range(read_count):
|
||||
for line in file:
|
||||
self.assertEqual(line, "")
|
||||
|
||||
with self.open(os_helper.TESTFN, "w+") as f:
|
||||
barrier = threading.Barrier(thread_count + 1)
|
||||
reader = threading.Thread(target=reader, args=(f, barrier))
|
||||
writers = [threading.Thread(target=writer, args=(f, barrier))
|
||||
for _ in range(thread_count)]
|
||||
with threading_helper.catch_threading_exception() as cm:
|
||||
with threading_helper.start_threads(writers + [reader]):
|
||||
pass
|
||||
self.assertIsNone(cm.exc_type)
|
||||
|
||||
self.assertEqual(os.stat(os_helper.TESTFN).st_size,
|
||||
write_count * thread_count)
|
||||
|
||||
|
||||
class CIOTest(IOTest):
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Fix crash when iterating over lines in a text file on the :term:`free threaded <free threading>` build.
|
|
@ -1578,6 +1578,8 @@ _io_TextIOWrapper_detach_impl(textio *self)
|
|||
static int
|
||||
_textiowrapper_writeflush(textio *self)
|
||||
{
|
||||
_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self);
|
||||
|
||||
if (self->pending_bytes == NULL)
|
||||
return 0;
|
||||
|
||||
|
@ -3173,8 +3175,9 @@ _io_TextIOWrapper_close_impl(textio *self)
|
|||
}
|
||||
|
||||
static PyObject *
|
||||
textiowrapper_iternext(PyObject *op)
|
||||
textiowrapper_iternext_lock_held(PyObject *op)
|
||||
{
|
||||
_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op);
|
||||
PyObject *line;
|
||||
textio *self = textio_CAST(op);
|
||||
|
||||
|
@ -3210,6 +3213,16 @@ textiowrapper_iternext(PyObject *op)
|
|||
return line;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
textiowrapper_iternext(PyObject *op)
|
||||
{
|
||||
PyObject *result;
|
||||
Py_BEGIN_CRITICAL_SECTION(op);
|
||||
result = textiowrapper_iternext_lock_held(op);
|
||||
Py_END_CRITICAL_SECTION();
|
||||
return result;
|
||||
}
|
||||
|
||||
/*[clinic input]
|
||||
@critical_section
|
||||
@getter
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue