-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
std: Introduce Io
Interface
#25592
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
base: master
Are you sure you want to change the base?
std: Introduce Io
Interface
#25592
Conversation
/// Copied and then passed to `start`. | ||
context: []const u8, | ||
context_alignment: std.mem.Alignment, | ||
start: *const fn (*Group, context: *const anyopaque) void, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a particular reason for passing the *Group
as a separate parameter to start
(which is not needed by the majority of call sites) instead of just having Select(...).async
add it to the args in context
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe I'm not understanding the suggestion, but that kind of sounds like a pain in the ass and unclear that it will generate better code
context_alignment: std.mem.Alignment, | ||
start: *const fn (context: *const anyopaque, result: *anyopaque) void, | ||
) error{OutOfMemory}!*Io.AnyFuture { | ||
if (builtin.single_threaded) unreachable; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we get an @compileError
with a message along the lines of "std.Io.Threaded.concurrent not supported in single threaded mode" or a comptime unreachable
(to turn this into a compile error instead of runtime error)?
That would make it easier (and faster) to notice what's wrong if the call to concurrent is in some dependency instead of done by yourself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I'm glad you reminded me about this. That specific suggestion won't work because it would cause a compilation failure when -fsingle-threaded
is used because it is only runtime-known whether the function is called.
However, I was thinking about making it return error.ConcurrencyUnavailable
rather than asserting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would work too, I guess, even a panic would (it is some sort of programmer error after all).
My main concern here is debuggability since unreachable tells you that some went wrong, but finding out what did can take quite a bit of time in a big-ish call stack.
const future = @field(s, field.name); | ||
any_future.* = future.any_future orelse return @unionInit(U, field.name, future.result); | ||
} | ||
switch (io.vtable.select(io.userdata, &futures)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
io.vtable.select
can return error.Canceled
.
switch (io.vtable.select(io.userdata, &futures)) { | |
switch (try io.vtable.select(io.userdata, &futures)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll solve this by adding a unit test, thanks!
|
||
/// `s` is a struct with every field a `*Future(T)`, where `T` can be any type, | ||
/// and can be different for each field. | ||
pub fn select(io: Io, s: anytype) SelectUnion(@TypeOf(s)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pub fn select(io: Io, s: anytype) SelectUnion(@TypeOf(s)) { | |
pub fn select(io: Io, s: anytype) Cancelable!SelectUnion(@TypeOf(s)) { |
The doc comment for select
should also probably be updated to describe when error.Canceled
is returned. Based on my own understanding of how select
should behave, maybe something like /// Returns `error.Canceled` if all futures in `s` are canceled.
would work?
which is planned to have all I/O operations in the interface, but for now has only async and await.
sorry, something still not working correctly
When the previous fiber did not request to be registered as an awaiter, it may not have actually been a full blown `Fiber`, so only create the `Fiber` pointer when needed.
This avoids needing to store more sizes and alignments. Only the result alignment needs to be stored, because `Fiber` is at a fixed zero offset.
Something is horribly wrong with scheduling, as can be seen in the debug output, but at least it somehow manages to exit cleanly...
How silly of me to forget that the kernel doesn't implement its own API. The scheduling is not great, but at least doesn't deadlock or hammer.
`std.Io.Evented` is introduced to select an appropriate Io implementation depending on OS
this API sucks but it's the best we've got on some operating systems
before, the max length of the host name depended on the target.
- ILSEQ -> error.BadPathName - implement dirStatPath for WASI
none of these APIs are documented to return this error code, but it would be cool if they did.
Move std.posix logic over rather than calling into it.
this value should be calculated earlier and passed in
This patchset adds
std.Io
and provides two implementations for it:-fno-single-threaded
- supports concurrency and cancellation.-fsingle-threaded
- does not support concurrency or cancellation.is an IoUring implementation for Linux, and nothing else yet. This API is not
ready to be used yet, but it serves to inform the evolution of
std.Io
API.std.Io.Threaded
has networking and file-system operations implemented.Cancellation works beautifully, except for a known race condition that has a
couple of competing solutions already in mind.
All of
std.net
has been deleted in favor ofstd.Io.net
.std.fs
has been partially updated to usestd.Io
- only as required so thatstd.Io.Writer.sendFile
could use*std.Io.File.Reader
rather than*std.fs.File.Reader
.closes #8224
Laundry List of Io Features
async
/await
- these primitives express that operations can be doneindependently, making them infallible and support execution on limited Io
implementations that lack a concurrency mechanism.
concurrent
- same asasync
except communicates that the operationmust be done concurrently for correctness. Requires memory allocation.
cancel
- equivalent toawait
except also requests the Io implementationto interrupt the operation and return
error.Canceled
.std.Io.Threaded
supports cancellation by sending a signal to a thread, causing blocking
syscalls to return
EINTR
, giving a chance to notice the cancellation request.select
- API for blocking on multiple futures usingswitch
syntaxGroup
- efficiently manages many async tasks. Supports waiting for andcancelling all tasks in the group together.
Queue(T)
- Many producer, many consumer, thread-safe, runtime configurablebuffer size. When buffer is empty, consumers suspend and are resumed by
producers. When buffer is full, producers suspend and are resumed by consumers.
TypeErasedQueue
.Select
- for blocking on runtime-known number of tasks and handling asubset of them.
Clock
,Duration
,Timestamp
,Timeout
- type safety for units of measurementMutex
,Condition
- synchronization primitivesDemo
Here is an example that makes an HTTP request to a domain:
Thanks to the fact that networking is now taking advantage of the new
std.Io
interface,this code has the following properties:
returned IP address.
attempts are canceled, including DNS queries.
-fsingle-threaded
even though theoperations happen sequentially.
You can see how this is implemented in
std.Io.net.HostName.connect
:Upgrade Guide
Missing
io
ParameterIf you need an
io
parameter, and you don't have one, you can get one like this:This is legal as long as these functions are not called:
Io.VTable.async
Io.VTable.concurrent
Io.VTable.groupAsync
This is a temporary workaround - a lot like reaching for
std.heap.page_allocator
whenyou need an
Allocator
and do not have one. Instead, it is better to accept anIo
parameter if you need one (or store one on a context struct for convenience).Point is that the application's
main
function should generally be responsible forconstructing the
Io
instance used throughout.When you're testing you can use
std.testing.io
(much likestd.testing.allocator
).Related
Merge Blockers
concurrent
return an error rather than asserting?Followup Issues