Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Custom event loops #9922

Open
zenith391 opened this issue Oct 8, 2021 · 1 comment
Open

Proposal: Custom event loops #9922

zenith391 opened this issue Oct 8, 2021 · 1 comment
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. standard library This issue involves writing Zig code for the standard library.
Milestone

Comments

@zenith391
Copy link
Contributor

One thing that made sense and that was made clear in #8224 was that one event loop implementation cannot fit all uses. Even a configurable event loop would just end up being very complex and still lack some niche requirement for an event loop. For example, an I/O heavy application might want to use an event loop that could be backed by APIs like io_uring, whereas a CPU-bound application might want to have finer grained control of resources, have less yield overhead, use a priority queue, etc.

Currently, it is possible to make a custom event loop, however it doesn't work with the standard library I/O abstractions, which means I/O have to be reimplemented using std.os in order to be integrated with the custom event loop. This is less than optimal, produces error-prone code and is duplicating code from the standard library to a program.

What I propose here is an API to use custom event loop implementations which could work the same way that currently a function can take a reader or a writer (using anytype) or the way a function takes an allocator (using "interfaces").
If a program wants to be synchronous, it would simply have to pass around a loop implementation that executes everything synchronously instead of using an opaque io_mode variable.

One important question if we want to implement it is whether to pass the event loop as an argument (like we currently do with allocators) or pass it in a global variable (which is currently done with io_mode)

If we pass it as an argument it could make some APIs feel "heavier" to use, but hopefully the way reader and writer are abstracted (and common sense that it isn't generally good idea to mix two different event loops for reading and writing) means that the context (for example, File) would handle the event loop. Opening a file could look like this:

const file = try std.fs.cwd().openFile("some_file", .{ .loop = someEventLoop });
const reader = file.reader();
_ = try reader.readBytesNoEof(14);

As you can see, one major flaw is that now whether it is asynchronous or not is now heavily visible, this can be viewed as a flaw because it makes async functions "colored", or it could be seen as following the "No hidden control flow." zen. Comments on that would be much appreciated.

Another way is to keep the global variable, in which the case the above code doesn't change from what we currently do today:

const file = try std.fs.cwd().openFile("some_file", .{});
const reader = file.reader();
_ = try reader.readBytesNoEof(14);

Like it is in the current status quo, looking at this code, reader.readBytesNoEof could either start an async function, wait for I/O using epoll or some other API, and return, or it could use the synchronous read system call.
However this might not be actually a bad thing or a case of hidden control flow, as the outcome from the function is usually the same.

Anyway, this is more of a request for comments than a direct proposal. So I'm open for any critique, point to make, or other.

@zenith391 zenith391 changed the title Proposal: Event loop choice Proposal: Custom event loops Oct 8, 2021
@andrewrk andrewrk added this to the 0.10.0 milestone Nov 20, 2021
@andrewrk andrewrk added proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. standard library This issue involves writing Zig code for the standard library. labels Nov 20, 2021
@tecanec
Copy link
Contributor

tecanec commented Jan 20, 2023

I'm in favor of having the function accept the event loop as a parameter. If I want a global event loop, I can just pass that to the function myself. I don't think it'd be any worse than std.mem.Allocator.

Plus, if I want multiple event loops, I can do that. Maybe I want to have different loops for real-time versus background tasks or something like that; I wouldn't want my autosave feature to cause lag spikes during gameplay, for example.

The rigidity of the event loop is honestly one of my least-favorite features of the std library. Especially since so much of the library depends on it. I can't use async file I/O without either writing them myself (which defeats the point of even having them in stdlib), or using the stdlib event loop (which may not suit my needs). It's also one of the only instances of global state in the library that aren't just wrappers around platform-specific stuff.

Simple wrappers for things like io_uring are okay, though, as long as I'm the one passing them to the async I/O functions. And I think it makes sense to keep the current event loop in a manner similar to std.heap.GeneralPurposeAllocator.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. standard library This issue involves writing Zig code for the standard library.
Projects
None yet
Development

No branches or pull requests

3 participants