Skip to content

runtime: I/O API + simulator - #5658

Draft
kim wants to merge 16 commits into
masterfrom
kim/sim-io
Draft

runtime: I/O API + simulator#5658
kim wants to merge 16 commits into
masterfrom
kim/sim-io

Conversation

@kim

@kim kim commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Adds an io-uring-compatible (file) I/O API with simulator and tokio impls.

The DST runtime uses the simulator to drive I/O operations and inject failures according to a configuration. Failure injection is more or less for demonstration purposes, and may need to become more sophisticated for actual DST.

It is not yet clear how the application-facing API should look like: in any case, we will need a way to enter the runtime context.

@kim kim changed the title WIP: I/O API + simulator runtime: I/O API + simulator Aug 7, 2026
@kim
kim requested a review from Shubham8287 August 7, 2026 13:33
@kim
kim marked this pull request as ready for review August 7, 2026 13:33
@kim
kim force-pushed the kim/sim-io branch 3 times, most recently from cb79364 to 031d709 Compare August 8, 2026 11:37
Comment thread crates/runtime-core/src/sim/executor/io.rs Outdated

@Shubham8287 Shubham8287 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.

This seems really good, I just think we should have sim I/O ops to use time to simulate latencies, Which is also required for sim executor to make progress correctly.

We can merge this PR, and do it in next PR though (Before we start using uring interface)

@kim
kim marked this pull request as draft August 31, 2026 12:57
@kim

kim commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

Putting this back into draft as I had to rework it substantially.

Namely, in order to support concurrency (like join_all or FuturesUnordered), the compiler needs to know the type of the operation futures and they need to unify. I.e. -> impl Future can't be stored in a list.

To do that, then, the B: AlignedBytes buffers also need to unify, which lead me down the road of passing pointers to heap allocations down the executor. Incidentally, this is what we'd have to do with actual io-uring. The downside is that *mut u8 is !Send, which keeps us honest but makes this machinery hard to integrate with the current runtime.

I don't think it's a big issue, because the API itself must come from somewhere, and we only really need the runtime to drive the completion futures (i.e. oneshot channels).

I'm keeping the PR in draft for now, as I think the SpacetimeIO API could potentially be changed to hide more of those details (and potentially also support linked batch operations, which could turn out useful).

@Shubham8287

Shubham8287 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

, which lead me down the road of passing pointers to heap allocations down the executor.

Do you consider ErasedBox as long term solution? asking because, Boxing requires alloc.

@kim

kim commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Do you consider ErasedBox as long term solution? asking because, Boxing requires alloc?

I don't think there is any other solution if we want it to work with actual io-uring.

I'm imagining that the pre-allocated memory is allocated on the heap (e.g. Vec<Box<Block>>>). I'll need to probably change the API to take Box<impl AlignedBytes> everywhere.

@Shubham8287

Copy link
Copy Markdown
Contributor

I'm imagining that the pre-allocated memory is allocated on the heap (e.g. Vec<Box>>). I'll need to probably change the API to take Box everywhere.

Yeah, I was expecting something in that direction and, I also think Vec could be problematic to divide and move around due to borrow checker so maybe some link-list type data structure.

Though, None of it needs to be addressed in this PR.

fn maybe_write_fault(&self, rng: &Rng, now: Instant, page_offset: usize) -> Option<WriteFault>;
}

pub struct Executor<const MAX_INFLIGHT: usize, UserData> {

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.

Do you think it will be too sophisticated to put concurrency limit on individual code components and treating the cap as invariant in executor? rather than doing back-pressure from executor which will be unpredictable and awkward.

I see, several benefits of it doing it at components level: We will know exactly what part is taking more than required resource, and we will have more granular control on things like snapshot witting would not be able to block archival, etc.

Though i am not sure about practicality of it, so I am figuring that out, seeking your thoughts.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah this should probably just be a Vec. But note that the submission and completion queues are bounded (the ring in uring).

Typically this is not very large (1024 is considered large), so if we can't predict the number of iops (which I think we can't), we'll need to put a semaphore in front.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ah uhm no, it works a bit differently: the SQ size only limits the batch size basically, and then the kernel executes stuff until it runs out of resources. There is never submission backpressure if the CQ overflows, completions will either be dropped or just appear on the CQ later.

I think we should never use IORING_FEAT_NODROP, so either panicking or dropping CQEs seem reasonable modes for the simulator.

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 think we should never use IORING_FEAT_NODROP, so either panicking or dropping CQEs seem reasonable modes for the simulator.

Should not we put custom backpressure in submission queue to avoid both panicking and dropping CQEs in prod implementation. Which implies simulator should always be panicking in that case?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think dropping could still be a valuable fault injection mode, because it makes completion channels pending forever without dropping the sender.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants