Skip to content

gh-85393: io: Handle partial writes in TextIOWrapper - #161306

Open
methane wants to merge 2 commits into
python:mainfrom
methane:gh-85393-textio-short-writes
Open

methane wants to merge 2 commits into
python:mainfrom
methane:gh-85393-textio-short-writes

Conversation

@methane

@methane methane commented Nov 4, 2026 •

Copy link
Copy Markdown
Member

@methane
methane requested a review from cmaloney as a code owner November 4, 2026 10:26
@methane methane added type-bug An unexpected behavior, bug, or error stdlib Standard Library Python modules in the Lib/ directory topic-IO needs backport to 3.14 bugs and security fixes needs backport to 3.15 bugs and security fixes labels Nov 4, 2026
@methane methane changed the title gh-85393: Handle partial writes in TextIOWrapper gh-85393: io: Handle partial writes in TextIOWrapper Nov 4, 2026

@cmaloney cmaloney left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think the added complexity is worthwhile for this use case... I would much rather get people to on top of a BufferedIO always / by default. That does the retry loop. Part of that is supporting buffer_size=0 in Buffered I/O which is in my plans for 3.16

FileIO is very intentionally POSIX I/O semantics and write length needs to be checked. Passing it other places despite the API having the same "shape" results in issues. I would much rather reduce cases where people think it is needed.

@methane

methane commented Nov 6, 2026

Copy link
Copy Markdown
Member Author

I was thinking in the opposite direction. Using BufferedWriter solely for its retry loop introduces too much overhead relative to the benefit.

Currently, TextIOWrapper bypasses BufferedWriter only in unbuffered mode. However, since the C implementation of TextIOWrapper already handles line buffering internally, we could improve I/O performance by bypassing BufferedWriter in line-buffering mode as well.

I was planning to propose bypassing BufferedWriter in line-buffering mode too, provided that _pyio is removed or that further divergence between the C implementation and _pyio is considered acceptable.

@cmaloney

cmaloney commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

I have plans to rewrite BufferedWriter for 3.16 and with the rewrite the overhead is <1%. Most of that is "we allocate one extra object compared to not using BufferedIO". The amount of code complexity things keep adding to avoid a buffered is a lot more work than that 1% and has led to significant bugs such as corrupted partially-written .pyc.

@methane

methane commented Nov 6, 2026

Copy link
Copy Markdown
Member Author

When I investigated this previously, I found that the overhead of acquiring locks on each method call through TextIOWrapper → BufferedWriter → FileIO accounted for a non-negligible portion of the execution time in line-buffered mode. I concluded that bypassing BufferedWriter could improve performance.

I don't remember the exact numbers or the test conditions, such as whether the benchmark was multithreaded. It was quite some time ago, so the situation may have changed since then. (At the very least, it was not a free-threaded build.)

If BufferedWriter can indeed be made faster, this change would be unnecessary. We could explicitly document the requirement that the buffer's write() method passed to TextIOWrapper must write the entire input in a single call, and use BufferedWriter even in unbuffered mode. That would allow us to close this issue.

@cmaloney

cmaloney commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

I agree by bypassing you can get ~15% performance back. That is not inherent to "buffering" as much as "the way BufferedWriter is written". I have been working on a comprehensive set of fixes and decided that a rewrite was going to be more straightforward than trying to adapt the current code. Presented that at the core sprint and plan to have more public updates soon. This is an ongoing project for the last 2+ years; steadily I have worked on reducing overheads and understanding the core constraints. The current public place to look at is https://discuss.python.org/t/reworking-buffered-i-o-in-cpython/90019/16. The slides I made for the core sprint I will get posted publicly soon. That thread will be updated when I do.

The new model I'm aiming to ship I call "nibbler" and is focused on a list of buffers, much like TextIOWrapper has currently. The list of buffers could be per-thread and/or per-task (https://docs.python.org/3.15/library/contextvars.html#module-contextvars) allowing multi thread scalable I/O although that is a significant behavior change from today so not an exposed feature in the initial rewrite. In the rewrite aiming for preserve compatibility, enable new features, and run faster. That requires at least one PEP, probably 2-3 which I am currently figuring out structure for and writing. Should have posted by end of month..

One of my stretch goals is if the str has utf-8 bytes in it and we're writing through a utf-8 configured TextIOWrapper I can get that buffer to the OS with zero additional copies :). Lots of different projects that need to coalesce for that but certain it is doable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting core review needs backport to 3.14 bugs and security fixes needs backport to 3.15 bugs and security fixes stdlib Standard Library Python modules in the Lib/ directory topic-IO type-bug An unexpected behavior, bug, or error

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants