GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-119506: fix `_io.TextIOWrapper.write()` write during flush by chgnrdv · Pull Request #119507 · python/cpython

Conversation

@chgnrdv

@chgnrdv chgnrdv commented May 24, 2024 •
edited by bedevere-app Bot
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Fixes #119506

  • check if call to _textiowrapper_writeflush() has left any data in self->pending_bytes. If so, store them in self->pending_bytes after b
  • add test
  • Issue: _io.TextIOWrapper.write: write during flush causes pending_bytes length mismatch #119506

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Eclips4 reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
@chgnrdv
gh-119506: fix _io.TextIOWrapper.write() write during flush
5b924dc
* check if call to `_textiowrapper_writeflush()` has left any data in `self->pending_bytes`. If so, store them in `self->pending_bytes` after `b`
* add test
@bedevere-app bedevere-app Bot added the awaiting review label May 24, 2024
@bedevere-app bedevere-app Bot mentioned this pull request May 24, 2024
_io.TextIOWrapper.write: write during flush causes pending_bytes length mismatch #119506
Closed
@chgnrdv

chgnrdv commented May 24, 2024

Copy link
Copy Markdown
Contributor Author

cc @methane @eryksun

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

blurb-it Bot and others added 4 commits May 24, 2024 14:32
@blurb-it
📜🤖 Added by blurb_it.
f8fd659
@chgnrdv
Update 2024-05-24-14-32-24.gh-issue-119506.-nMNqq.rst
4809c1a
@chgnrdv
Merge branch 'main' into io-textiowrapper-fix-write-during-flush
e551b86
@chgnrdv
Update 2024-05-24-14-32-24.gh-issue-119506.-nMNqq.rst
2e72b28
@methane methane added 3.12 only security fixes 3.13 only security fixes 3.14 bugs and security fixes needs backport to 3.12 only security fixes needs backport to 3.13 only security fixes labels May 24, 2024
@methane

methane commented May 24, 2024

Copy link
Copy Markdown
Member

Thank you. The patch looks good to me.

chgnrdv reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@chgnrdv

chgnrdv commented May 24, 2024

Copy link
Copy Markdown
Contributor Author

Now it crashes if any of these calls fails: length ofself->pending_bytes doesn't match self->pending_bytes_count on return.

cpython/Modules/_io/textio.c

Line 1735 in bf5b646

PyObject *list = PyList_New(2);

cpython/Modules/_io/textio.c

Line 1745 in bf5b646

if (PyList_Append(self->pending_bytes, b) < 0) {

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

chgnrdv and others added 3 commits May 24, 2024 19:23
@chgnrdv
refactor
43628ce
@chgnrdv
Update 2024-05-24-14-32-24.gh-issue-119506.-nMNqq.rst
a3fe68e
@chgnrdv
Update 2024-05-24-14-32-24.gh-issue-119506.-nMNqq.rst
c246d7f
methane
methane reviewed May 25, 2024
View reviewed changes
Comment thread Modules/_io/textio.c Outdated
Comment on lines +1730 to +1736
if (self->pending_bytes) {
// gh-119506: call to _textiowrapper_writeflush()
// can write new data to pending_bytes
pending = self->pending_bytes;
self->pending_bytes = b;
b = pending;
}

@methane methane May 25, 2024

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do you swap data here?

  • self->pending can be list
  • call from other thread might be returned already.
    • "first returned first write" seems correct than "first called first write".

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@elistevens elistevens May 30, 2024

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you look at the test case, where flushing the stream triggers a write, it's not a threading thing, it's because of write being called recursively.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@methane methane Jun 1, 2024

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even single thread case, it is not possble to guarantee "first called, first written."
Simple implementation is more important.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
methane
methane reviewed May 25, 2024
View reviewed changes
Comment thread Modules/_io/textio.c Outdated
else if (self->pending_bytes_count + bytes_len > self->chunk_size) {
PyObject *pending = self->pending_bytes;

if (self->pending_bytes_count + bytes_len > self->chunk_size) {

@methane methane May 25, 2024

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about while instead of if?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@elistevens elistevens May 30, 2024

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would also need a check to make sure that bytes_len > self->chunk_size isn't true.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@elistevens

elistevens commented May 30, 2024

Copy link
Copy Markdown

This PR seems to be addressing a similar issue as #119107 but I don't think that this PR fixes the race condition present when multi-threaded writes happen.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@elistevens

elistevens commented May 30, 2024

Copy link
Copy Markdown

After fixing my original test case, I think that this minimal patch addresses the issue in practical terms:

diff --git a/Modules/_io/textio.c b/Modules/_io/textio.c
index 3de4c06704..e084d04428 100644
--- a/Modules/_io/textio.c
+++ b/Modules/_io/textio.c
@@ -1701,16 +1701,16 @@ _io_TextIOWrapper_write_impl(textio *self, PyObject *text)
         bytes_len = PyBytes_GET_SIZE(b);
     }
 
-    if (self->pending_bytes == NULL) {
-        self->pending_bytes_count = 0;
-        self->pending_bytes = b;
-    }
-    else if (self->pending_bytes_count + bytes_len > self->chunk_size) {
+    if (self->pending_bytes_count + bytes_len > self->chunk_size) {
         // Prevent to concatenate more than chunk_size data.
         if (_textiowrapper_writeflush(self) < 0) {
             Py_DECREF(b);
             return NULL;
         }
+    }
+
+    if (self->pending_bytes == NULL) {
+        assert(self->pending_bytes_count == 0);
         self->pending_bytes = b;
     }
     else if (!PyList_CheckExact(self->pending_bytes)) {

there is still an issue where since there isn't a lock AFAIK, another thread could modify self->pending_bytes between check and assignment here:

    if (self->pending_bytes == NULL) {
        assert(self->pending_bytes_count == 0);
        self->pending_bytes = b;
    }

but that doesn't seem to be an issue in practice.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@elistevens

elistevens commented May 30, 2024 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

I should note that there's a similar race condition issue in _textiowrapper_writeflush where we don't ensure that the list hasn't been modified between for (Py_ssize_t i = 0; i < PyList_GET_SIZE(pending); i++) { and self->pending_bytes = NULL;.

That's a bit more scary, given that there's an entire list iteration between that PyList_GET_SIZE(pending) call and nuking the entire list. #119107 tries to address that, though there's still a potential race with that change as well (but I have yet to actually provoke that issue with or without the change to writeflush, so it's largely theoretical at this point).

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@elistevens elistevens mentioned this pull request May 30, 2024
gh-118138: Adds test for multithreaded writes to files. #119107
Closed
@methane
fixup
156940d
@methane

methane commented May 31, 2024

Copy link
Copy Markdown
Member

I should note that there's a similar race condition issue in _textiowrapper_writeflush where we don't ensure that the list hasn't been modified between for (Py_ssize_t i = 0; i < PyList_GET_SIZE(pending); i++) { and self->pending_bytes = NULL;.

Note that default Python build with GIL doesn't have such race.

_textiowrapper_writeflush releases GIL when it calls buffer.write():

    self->pending_bytes_count = 0;
    self->pending_bytes = NULL;
    Py_DECREF(pending);

    PyObject *ret;
    do {
        ret = PyObject_CallMethodOneArg(self->buffer, &_Py_ID(write), b);
    } while (ret == NULL && _PyIO_trap_eintr());

Other thread may call TextIOWrapper.write(b) and it appends b to self->pending_bytes.
We can not assume self->pending_bytes == NULL and self->pending_bytes_count == 0 after _textiowrapper_writeflush. That was the bug.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@methane
fix test
2392d20
@elistevens

elistevens commented May 31, 2024

Copy link
Copy Markdown

Note that default Python build with GIL doesn't have such race.
_textiowrapper_writeflush releases GIL when it calls buffer.write()

Ah, that makes sense. Thank you for explaining!

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

methane added 2 commits June 1, 2024 12:26
@methane
add assert
02cf53a
@methane
simplify code and write more comments
483975c
methane added 2 commits June 1, 2024 13:35
@methane
update test
01327f7
@methane
update changelog
26eadbe
@methane
methane merged commit 52586f9 into python:main Jun 3, 2024
@bedevere-app bedevere-app Bot removed the awaiting review label Jun 3, 2024
@miss-islington-app

miss-islington-app Bot commented Jun 3, 2024

Copy link
Copy Markdown

Thanks @chgnrdv for the PR, and @methane for merging it 🌮🎉.. I'm working now to backport this PR to: 3.12, 3.13.
🐍🍒⛏🤖

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Jun 3, 2024
@chgnrdv @methane @miss-islington
pythongh-119506: fix _io.TextIOWrapper.write() write during flush (p…
9852e17
…ythonGH-119507)

(cherry picked from commit 52586f9)

Co-authored-by: Radislav Chugunov <[email protected]>
Co-authored-by: Inada Naoki <[email protected]>
@bedevere-app

bedevere-app Bot commented Jun 3, 2024

Copy link
Copy Markdown

GH-119964 is a backport of this pull request to the 3.13 branch.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-app bedevere-app Bot removed the needs backport to 3.13 only security fixes label Jun 3, 2024
miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Jun 3, 2024
@chgnrdv @methane @miss-islington
pythongh-119506: fix _io.TextIOWrapper.write() write during flush (p…
30d05b3
…ythonGH-119507)

(cherry picked from commit 52586f9)

Co-authored-by: Radislav Chugunov <[email protected]>
Co-authored-by: Inada Naoki <[email protected]>
@bedevere-app

bedevere-app Bot commented Jun 3, 2024

Copy link
Copy Markdown

GH-119965 is a backport of this pull request to the 3.12 branch.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-app bedevere-app Bot removed the needs backport to 3.12 only security fixes label Jun 3, 2024
mliezun pushed a commit to mliezun/cpython that referenced this pull request Jun 3, 2024
@chgnrdv @methane @mliezun
pythongh-119506: fix _io.TextIOWrapper.write() write during flush (p…
9a639ad
…ython#119507)

Co-authored-by: Inada Naoki <[email protected]>
@elistevens

elistevens commented Jun 4, 2024

Copy link
Copy Markdown

Thanks for bringing this home! Does this qualify as a security issue? I'm wondering if it will be backported to 3.10.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

methane
methane reviewed Jun 5, 2024
View reviewed changes
Comment thread Modules/_io/textio.c
}
// Since Python 3.12, allocating GC object won't trigger GC and release
// GIL. See https://github.com/python/cpython/issues/97922
assert(!PyList_CheckExact(self->pending_bytes));

@methane methane Jun 5, 2024

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure about this patch will be backported to security branches.
If it will, this line needs additional fixes. So backport is not simple.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
barneygale pushed a commit to barneygale/cpython that referenced this pull request Jun 5, 2024
@chgnrdv @methane @barneygale
pythongh-119506: fix _io.TextIOWrapper.write() write during flush (p…
cdebef6
…ython#119507)

Co-authored-by: Inada Naoki <[email protected]>
@methane methane mentioned this pull request Jun 10, 2024
[3.11] gh-119506: fix _io.TextIOWrapper.write() write during flush (#119507) #120314
Merged
@bedevere-app

bedevere-app Bot commented Jun 10, 2024

Copy link
Copy Markdown

GH-120314 is a backport of this pull request to the 3.11 branch.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

kumaraditya303 pushed a commit that referenced this pull request Jun 19, 2024
@miss-islington @chgnrdv @methane
[3.13] gh-119506: fix _io.TextIOWrapper.write() write during flush (G…
9be94f9
…H-119507) (#119964)

gh-119506: fix `_io.TextIOWrapper.write()` write during flush (GH-119507)
(cherry picked from commit 52586f9)

Co-authored-by: Radislav Chugunov <[email protected]>
Co-authored-by: Inada Naoki <[email protected]>
kumaraditya303 pushed a commit that referenced this pull request Jun 19, 2024
@miss-islington @chgnrdv @methane
[3.12] gh-119506: fix _io.TextIOWrapper.write() write during flush (G…
cde976d
…H-119507) (#119965)

gh-119506: fix `_io.TextIOWrapper.write()` write during flush (GH-119507)
(cherry picked from commit 52586f9)

Co-authored-by: Radislav Chugunov <[email protected]>
Co-authored-by: Inada Naoki <[email protected]>
estyxx pushed a commit to estyxx/cpython that referenced this pull request Jul 17, 2024
@chgnrdv @methane @estyxx
pythongh-119506: fix _io.TextIOWrapper.write() write during flush (p…
c734178
…ython#119507)

Co-authored-by: Inada Naoki <[email protected]>
@mhsmith mhsmith mentioned this pull request Aug 5, 2024
gh-116622: Android logging fixes #122698
Merged
hugovk added a commit that referenced this pull request Aug 9, 2024
@methane @hugovk
[3.11] gh-119506: fix _io.TextIOWrapper.write() write during flush (#…
8a978a7
…119507) (#120314)

Co-authored-by: Hugo van Kemenade <[email protected]>
fix _io.TextIOWrapper.write() write during flush (#119507)
@coderabbitai coderabbitai Bot mentioned this pull request Jul 22, 2026
OLS-3539 Replace dual-path audit with OTel-only span events openshift/lightspeed-service#3015
Merged
3 of 4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@methane methane methane left review comments
+1 more reviewer
@elistevens elistevens elistevens left review comments
Reviewers whose approvals may not affect merge requirements

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

3.12 only security fixes 3.13 only security fixes 3.14 bugs and security fixes

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

_io.TextIOWrapper.write: write during flush causes pending_bytes length mismatch

Uh oh!

There was an error while loading. Please reload this page.

3 participants

@chgnrdv @methane @elistevens
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.