Skip to content

LEP: Split tokio runtime and dedicated file I/O with async#145

Open
mjansson wants to merge 1 commit into
EpicGames:mainfrom
mjansson:mjansson/lep-tokio-split-async-io
Open

LEP: Split tokio runtime and dedicated file I/O with async#145
mjansson wants to merge 1 commit into
EpicGames:mainfrom
mjansson:mjansson/lep-tokio-split-async-io

Conversation

@mjansson

Copy link
Copy Markdown
Collaborator

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.

@mjansson
mjansson force-pushed the mjansson/lep-tokio-split-async-io branch from 49d69a1 to 13ee213 Compare July 24, 2026 19:57
@mjansson
mjansson marked this pull request as ready for review July 24, 2026 19:58

@matt-hoffman-epic matt-hoffman-epic left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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?

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants