LEP: Split tokio runtime and dedicated file I/O with async#145
Conversation
Signed-off-by: Mattias Jansson <[email protected]>
49d69a1 to
13ee213
Compare
matt-hoffman-epic
left a comment
There was a problem hiding this comment.
I don't have insight into the specifics behind io_uring and Windows' overlapped I/O, so I can't really approve the specifics of those being able to support a larger number of queued operations w/o requiring more threads.
But taking your word on that, this SGTM.
|
|
||
| **The blocking pool is untouched in this phase.** File I/O still runs on it at today's sizing; it shrinks to its residual only when phase 2 gives file I/O its own engine. Phase 1's thread reduction comes entirely from deleting the compute pool — one full CPU-count population — and its isolation win from the net split. | ||
|
|
||
| ### Phase 2 — the file I/O engine |
There was a problem hiding this comment.
This work intersects w/ the SWFS work to put file operations in Lore operations behind the FilesystemProvider interface.
I don't think it actually affects this plan directly, that interface has higher level operations like "read a Node from the ImmutableStore into a file" rather than "write to a file". But having this phase finished might simplify auditing code in the lore-revision crate.
|
|
||
| **Three backends behind one trait — a pooled baseline and two completion upgrades.** | ||
|
|
||
| - *psync* (portable baseline): the trait implemented with positional syscalls (`pread`/`pwrite`) executed on a dedicated bounded syscall pool — `min(2 × cores, 32)` threads, idle-reaped. Pooled rather than inline execution is deliberate: a syscall against a slow filesystem (cold media, NFS) blocks for milliseconds or indefinitely, and inline execution would let a handful of such operations freeze the entire core runtime, while pool threads — like today's blocking pool — absorb the wait. Seastar's dedicated syscall thread is the same answer to the same problem. This backend is not a mere fallback: it is the permanent engine on macOS, which offers no completion-based file I/O — kqueue does not cover regular-file data operations, and POSIX AIO and `dispatch_io` are thread pools underneath, which is why libuv and tokio also run file I/O on a pool there. On Linux it is selected when io_uring is unavailable, which is common, not exotic: Docker's default seccomp profile has blocked io_uring syscalls since 2023 (moby/moby#46762), and kernels older than 5.6 lack it entirely. Even at its cap the pool is a quarter of today's blocking-pool ceiling, and it is dedicated: file I/O no longer competes with anything else for its threads. The backend is probed once per process at startup; `LORE_IO_BACKEND` and a per-repository override (`io.backend` in the repository's `.lore/config.toml`) force a backend for diagnosis or unusual filesystems. There is no automatic per-mount detection: io_uring remains correct on network and FUSE filesystems (operations complete via io-wq), so backend choice never affects correctness, only performance. Flush-class operations are ordinary operations on the syscall pool. |
There was a problem hiding this comment.
Confirming I understand correctly:
This is "portable", but due to using posix calls that just means consistently available on *nix?
Windows will only have the single implementation because we know it's available?
Summary
This proposal replaces Lore's open-ended thread model with a fixed budget known at initialization, through three structural moves.
First, the network transport (QUIC, gRPC) moves onto a small dedicated runtime — two threads on the client; up to one per processor on the server, where thousands of concurrent client connections are the normal case — so protocol timers, TLS, and packet processing are never delayed by compute or I/O continuations saturating the core workers.
Second, the separate compute thread pool is deleted: compression, hashing, and chunking are fragment-sized work units (≤256 KiB, single-digit milliseconds worst case) that run inline in tasks on the per-processor core workers, removing a full CPU-count thread population.
Third, file I/O leaves the tokio blocking pool for a dedicated I/O engine: a bounded, idle-reaped pool of positional-syscall threads (
min(2 × cores, 32)) as the portable baseline — the permanent engine on macOS and the fallback wherever completion-based I/O is unavailable — upgraded to completion-based asynchronous I/O (io_uring on Linux, overlapped I/O on Windows) where the platform offers it, at which point data-plane parallelism — reads, writes, flushes — becomes operating-system queue depth instead of thread count, with the pool retained for metadata and composite operations. What remains of the core runtime's blocking pool is a residual ~4 threads serving OS APIs with no asynchronous form.