8000 [3.14] gh-134908: Protect `textiowrapper_iternext` with critical sect… · python/cpython@428b0ca · GitHub
[go: up one dir, main page]

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)
    0