-
-
Notifications
You must be signed in to change notification settings - Fork 30.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gh-119506: fix _io.TextIOWrapper.write()
write during flush
#119507
gh-119506: fix _io.TextIOWrapper.write()
write during flush
#119507
Conversation
* 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
Thank you. The patch looks good to me. |
Modules/_io/textio.c
Outdated
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; | ||
} |
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".
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.
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.
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) { |
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
?
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.
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. |
After fixing my original test case, I think that this minimal patch addresses the issue in practical terms:
there is still an issue where since there isn't a lock AFAIK, another thread could modify
but that doesn't seem to be an issue in practice. |
I should note that there's a similar race condition issue in That's a bit more scary, given that there's an entire list iteration between that |
Note that default Python build with GIL doesn't have such race.
Other thread may call TextIOWrapper.write(b) and it appends b to self->pending_bytes. |
Ah, that makes sense. Thank you for explaining! |
d8c203e
to
b2eb9d0
Compare
b2eb9d0
to
483975c
Compare
…ythonGH-119507) (cherry picked from commit 52586f9) Co-authored-by: Radislav Chugunov <[email protected]> Co-authored-by: Inada Naoki <[email protected]>
GH-119964 is a backport of this pull request to the 3.13 branch. |
…ythonGH-119507) (cherry picked from commit 52586f9) Co-authored-by: Radislav Chugunov <[email protected]> Co-authored-by: Inada Naoki <[email protected]>
GH-119965 is a backport of this pull request to the 3.12 branch. |
…ython#119507) Co-authored-by: Inada Naoki <[email protected]>
Thanks for bringing this home! Does this qualify as a security issue? I'm wondering if it will be backported to 3.10. |
@@ -1737,6 +1747,9 @@ _io_TextIOWrapper_write_impl(textio *self, PyObject *text) | |||
Py_DECREF(b); | |||
return NULL; | |||
} | |||
// 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)); |
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.
…ython#119507) Co-authored-by: Inada Naoki <[email protected]>
GH-120314 is a backport of this pull request to the 3.11 branch. |
…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]>
…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]>
…ython#119507) Co-authored-by: Inada Naoki <[email protected]>
…ython#119507) Co-authored-by: Inada Naoki <[email protected]>
…119507) (#120314) Co-authored-by: Hugo van Kemenade <[email protected]> fix _io.TextIOWrapper.write() write during flush (#119507)
Fixes #119506
_textiowrapper_writeflush()
has left any data inself->pending_bytes
. If so, store them inself->pending_bytes
afterb
_io.TextIOWrapper.write
: write during flush causespending_bytes
length mismatch #119506