Skip to content

Commit 428b0ca

Browse files
[3.14] gh-134908: Protect textiowrapper_iternext with critical section (gh-134910) (gh-135039)
The `textiowrapper_iternext` function called `_textiowrapper_writeflush`, but did not use a critical section, making it racy in free-threaded builds. (cherry picked from commit 44fb7c3) Co-authored-by: Duane Griffin <duaneg@dghda.com>
1 parent 7ac4618 commit 428b0ca

File tree

3 files changed

+46
-1
lines changed

3 files changed

+46
-1
lines changed

Lib/test/test_io.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,6 +1062,37 @@ def flush(self):
10621062
# Silence destructor error
10631063
R.flush = lambda self: None
10641064

1065+
@threading_helper.requires_working_threading()
1066+
def test_write_readline_races(self):
1067+
# gh-134908: Concurrent iteration over a file caused races
1068+
thread_count = 2
1069+
write_count = 100
1070+
read_count = 100
1071+
1072+
def writer(file, barrier):
1073+
barrier.wait()
1074+
for _ in range(write_count):
1075+
file.write("x")
1076+
1077+
def reader(file, barrier):
1078+
barrier.wait()
1079+
for _ in range(read_count):
1080+
for line in file:
1081+
self.assertEqual(line, "")
1082+
1083+
with self.open(os_helper.TESTFN, "w+") as f:
1084+
barrier = threading.Barrier(thread_count + 1)
1085+
reader = threading.Thread(target=reader, args=(f, barrier))
1086+
writers = [threading.Thread(target=writer, args=(f, barrier))
1087+
for _ in range(thread_count)]
1088+
with threading_helper.catch_threading_exception() as cm:
1089+
with threading_helper.start_threads(writers + [reader]):
1090+
pass
1091+
self.assertIsNone(cm.exc_type)
1092+
1093+
self.assertEqual(os.stat(os_helper.TESTFN).st_size,
1094+
write_count * thread_count)
1095+
10651096

10661097
class CIOTest(IOTest):
10671098

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix crash when iterating over lines in a text file on the :term:`free threaded <free threading>` build.

Modules/_io/textio.c

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1578,6 +1578,8 @@ _io_TextIOWrapper_detach_impl(textio *self)
15781578
static int
15791579
_textiowrapper_writeflush(textio *self)
15801580
{
1581+
_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self);
1582+
15811583
if (self->pending_bytes == NULL)
15821584
return 0;
15831585

@@ -3173,8 +3175,9 @@ _io_TextIOWrapper_close_impl(textio *self)
31733175
}
31743176

31753177
static PyObject *
3176-
textiowrapper_iternext(PyObject *op)
3178+
textiowrapper_iternext_lock_held(PyObject *op)
31773179
{
3180+
_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op);
31783181
PyObject *line;
31793182
textio *self = textio_CAST(op);
31803183

@@ -3210,6 +3213,16 @@ textiowrapper_iternext(PyObject *op)
32103213
return line;
32113214
}
32123215

3216+
static PyObject *
3217+
textiowrapper_iternext(PyObject *op)
3218+
{
3219+
PyObject *result;
3220+
Py_BEGIN_CRITICAL_SECTION(op);
3221+
result = textiowrapper_iternext_lock_held(op);
3222+
Py_END_CRITICAL_SECTION();
3223+
return result;
3224+
}
3225+
32133226
/*[clinic input]
32143227
@critical_section
32153228
@getter

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy