python-trio / python-trio/trio
Build our own file I/O API
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 7.3k
- Forks
- 431
- Avg merge
- 2d 17h
- Merged PRs (30d)
- 6
Description
Right now, our file I/O API is just a re-export of the one built into Python, with threads wrapped around all the I/O operations.
Python's file I/O API is very rich. For example, io.FileIO is a type of io.RawIOBase, which is a type of io.IOBase. And open by default returns a io.TextIOWrapper wrapped around an io.BufferedRandom wrapped around a io.FileIO object, with incremental unicode and newline decoding, custom buffering, etc. Reusing this code lets us isolate ourselves + our users from the details of low-level file I/O.
The downside, of course, is that if we don't like how Python is handling that low-level file I/O, there's not much we can do about it, because there are like 3 abstraction layers between us and the actual syscalls. Historically, this hasn't been a big deal, because there hasn't been any better option than running regular blocking syscalls in a thread. But the tide of history is changing.
First, linux added preadv2(..., RWF_NOWAIT), which is very simple -- it just lets you skip going to a thread if the data is already in cache; still have to go to a thread otherwise. But this is still enough for a dramatic speedup if you can use it. I was hoping that we could extend the io module to support this (see bpo-32561), but (a) this hasn't really gone anywhere, and (b) see next paragraph.
Then, io_uring came along, which is completely incompatible with the io module. And this article makes a compelling case that you really need a io_uring-like API to get reasonable performance on modern hardware; the RWF_NOWAIT trick isn't enough:
We also have this request to support FreeBSD's native aio API: #1953
So... it seems like sooner or later we need to give up on io and write our own async file API. What should that look like?
One option would be to copy the io API in detail, but... it's huge, so that would be difficult, and also... I'm not sure all the hair is really useful? One thing in particular I'm not a big fan of is that way everything is centered around the "current file position". This means every file object has some global state. Especially in a concurrent program, an API where you simply say which offset you want to read/write at each time seems better. (This is what Unix calls pread/pwrite, as opposed to read/write that use the "current position".) This would mean we can't support treating streaming data like sockets as files, the way the io module can, but... that seems fine.
What do you really need to do with files?
- read/write bytes at offset
- write bytes/text at end
- iterate through byte chunks
- iterate through text chunks
- iterate through text lines
- read the whole thing as a single big blob of bytes/text
- [edit to add] truncate
Is there anything else? Those all seem pretty simple, and don't require anything like the io module's elaborate inheritance hierarchy.
The article I linked above also links to a rust library for io_uring; they might have some useful API inspiration.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Review Python's io documentation, bpo-32561, issue #1953, the linked io_uring article, and the referenced Rust library for API inspiration. Compare the proposed offset-based and iteration operations, then define an agreed API that covers the required file behaviors and platform goals.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python, rust
- Domain
- api, backend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100