Recently I started dabbling with zig and in particular with the std.Io. Comming from the rust async world, where I use non mainstream async runtimes such as monoio or glommio (glommio will be subject of this topic in particular), I found the interface in it’s current shape to be biased towards certain implementations.
One of the inspirations for glommio was a C++ framework seastar, which uses scheduling groups. Each of the group has a configurable number of shares in order to proportionally divide CPU time across different type of work. Glommio api for spawning a task looks as follows (I’ve trimmed some extra stuff for simplicity):
// Create low priority task queue queue
let low_prio_tq = glommio::executor().create_task_queue(
Shares::Static(100),
Latency::NotImportant,
);
// Create high priority task queue queue
let high_prio_tq = glommio::executor().create_task_queue(
Shares::Static(1000),
Latency::NotImportant,
);
// Create the task (in zig) `io.async` / `io.concurrent` call on high priority task queue
let high_task = glommio::spawn_local_into(
high_priority_work(),
high_prio_tq,
)
.unwrap();
// Create the task (in zig) `io.async` / `io.concurrent` call on low priority task queue
let low_task = glommio::spawn_local_into(
low_priority_work(),
low_prio_tq,
)
.unwrap();
// Await those tasks (in zig) high_task.await(io); low_task.await(io);
high_task.await;
low_task.await;
As you can see from the perspective of consumer of that API, there is quite a lot of choice when it comes to giving the scheduler “hints”, each of those teach the scheduler how you’d like that particular class of work to behave.
I am still a noob when it comes to Zig, so my understanding is quite limited, but as far as I can grasp the current architecture of std.Io and having not found an example of a priority-aware std.Io implementation, I conjure in order to emulate such setup, one would need multiple std.Io instances, where each of them in the init function specify their priority class, but also have a way to communicate between each other and coordinate it’s own scheduling.
A potential solution to this problem would be to extend io.async and io.concurrent with an type erased context that one could pass to each of those calls, I am aware of this being very leaky and not elegant, but the entire problem space of desiging an unified Io interface is full of such tradeoffs (the reader/writer cancellation story).
Alternatively, the std.Io interface instead of relying on Io itself as the scheduling primitive, could require an Scheduler interface, which would be used for the async and concurrent calls, but then there is a question on how to design such interface so it satisfies all the possible implementations of an scheduler, I am intentionally leaving it vague, so somebody more experienced could interrogate that idea further and accept/reject it based on how feasible it is at all.