authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-03-24 18:43:53-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-02 16:30:59-07:00
log63c554d5a796d181f9b73ec19b8d9a22de98a1fc
tree8e454b1e44ed8d101a5ff1c6f9714c67c0c0686b
parentbc4da9a90743c11c7c0b3e485f46d365d57d87b7

introduce std.Io interface

which is planned to have all I/O operations in the interface, but for now has only async and await.

2 files changed, 72 insertions(+), 20 deletions(-)

lib/std/Io.zig+69
...@@ -553,3 +553,72 @@ test {...@@ -553,3 +553,72 @@ test {
553 _ = tty;553 _ = tty;
554 _ = @import("Io/test.zig");554 _ = @import("Io/test.zig");
555}555}
556
557const Io = @This();
558
559userdata: ?*anyopaque,
560vtable: *const VTable,
561
562pub const VTable = struct {
563 /// If it returns `null` it means `result` has been already populated and
564 /// `await` will be a no-op.
565 async: *const fn (
566 /// Corresponds to `Io.userdata`.
567 userdata: ?*anyopaque,
568 /// The pointer of this slice is an "eager" result value.
569 /// The length is the size in bytes of the result type.
570 eager_result: []u8,
571 /// Passed to `start`.
572 context: ?*anyopaque,
573 start: *const fn (context: ?*anyopaque, result: *anyopaque) void,
574 ) ?*AnyFuture,
575
576 /// This function is only called when `async` returns a non-null value.
577 await: *const fn (
578 /// Corresponds to `Io.userdata`.
579 userdata: ?*anyopaque,
580 /// The same value that was returned from `async`.
581 any_future: *AnyFuture,
582 /// Points to a buffer where the result is written.
583 /// The length is equal to size in bytes of result type.
584 result: []u8,
585 ) void,
586};
587
588pub const AnyFuture = opaque {};
589
590pub fn Future(Result: type) type {
591 return struct {
592 any_future: ?*AnyFuture,
593 result: Result,
594
595 pub fn await(f: *@This(), io: Io) Result {
596 const any_future = f.any_future orelse return f.result;
597 io.vtable.await(io.userdata, any_future, @ptrCast((&f.result)[0..1]));
598 f.any_future = null;
599 return f.result;
600 }
601 };
602}
603
604/// `s` is a struct instance that contains a function like this:
605/// ```
606/// struct {
607/// pub fn start(s: S) Result { ... }
608/// }
609/// ```
610/// where `Result` is any type.
611pub fn async(io: Io, s: anytype) Future(@typeInfo(@TypeOf(@TypeOf(s).start)).@"fn".return_type.?) {
612 const S = @TypeOf(s);
613 const Result = @typeInfo(@TypeOf(S.start)).@"fn".return_type.?;
614 const TypeErased = struct {
615 fn start(context: ?*anyopaque, result: *anyopaque) void {
616 const context_casted: *const S = @alignCast(@ptrCast(context));
617 const result_casted: *Result = @ptrCast(@alignCast(result));
618 result_casted.* = S.start(context_casted.*);
619 }
620 };
621 var future: Future(Result) = undefined;
622 future.any_future = io.vtable.async(io.userdata, @ptrCast((&future.result)[0..1]), @constCast(&s), TypeErased.start);
623 return future;
624}
lib/std/Thread/Pool.zig+3-20
...@@ -7,6 +7,7 @@ mutex: std.Thread.Mutex = .{},...@@ -7,6 +7,7 @@ mutex: std.Thread.Mutex = .{},
7cond: std.Thread.Condition = .{},7cond: std.Thread.Condition = .{},
8run_queue: std.SinglyLinkedList = .{},8run_queue: std.SinglyLinkedList = .{},
9is_running: bool = true,9is_running: bool = true,
10/// Must be a thread-safe allocator.
10allocator: std.mem.Allocator,11allocator: std.mem.Allocator,
11threads: if (builtin.single_threaded) [0]std.Thread else []std.Thread,12threads: if (builtin.single_threaded) [0]std.Thread else []std.Thread,
12ids: if (builtin.single_threaded) struct {13ids: if (builtin.single_threaded) struct {
...@@ -16,12 +17,12 @@ ids: if (builtin.single_threaded) struct {...@@ -16,12 +17,12 @@ ids: if (builtin.single_threaded) struct {
16 }17 }
17} else std.AutoArrayHashMapUnmanaged(std.Thread.Id, void),18} else std.AutoArrayHashMapUnmanaged(std.Thread.Id, void),
1819
19const Runnable = struct {20pub const Runnable = struct {
20 runFn: RunProto,21 runFn: RunProto,
21 node: std.SinglyLinkedList.Node = .{},22 node: std.SinglyLinkedList.Node = .{},
22};23};
2324
24const RunProto = *const fn (*Runnable, id: ?usize) void;25pub const RunProto = *const fn (*Runnable, id: ?usize) void;
2526
26pub const Options = struct {27pub const Options = struct {
27 allocator: std.mem.Allocator,28 allocator: std.mem.Allocator,
...@@ -117,12 +118,6 @@ pub fn spawnWg(pool: *Pool, wait_group: *WaitGroup, comptime func: anytype, args...@@ -117,12 +118,6 @@ pub fn spawnWg(pool: *Pool, wait_group: *WaitGroup, comptime func: anytype, args
117 const closure: *@This() = @alignCast(@fieldParentPtr("runnable", runnable));118 const closure: *@This() = @alignCast(@fieldParentPtr("runnable", runnable));
118 @call(.auto, func, closure.arguments);119 @call(.auto, func, closure.arguments);
119 closure.wait_group.finish();120 closure.wait_group.finish();
120
121 // The thread pool's allocator is protected by the mutex.
122 const mutex = &closure.pool.mutex;
123 mutex.lock();
124 defer mutex.unlock();
125
126 closure.pool.allocator.destroy(closure);121 closure.pool.allocator.destroy(closure);
127 }122 }
128 };123 };
...@@ -179,12 +174,6 @@ pub fn spawnWgId(pool: *Pool, wait_group: *WaitGroup, comptime func: anytype, ar...@@ -179,12 +174,6 @@ pub fn spawnWgId(pool: *Pool, wait_group: *WaitGroup, comptime func: anytype, ar
179 const closure: *@This() = @alignCast(@fieldParentPtr("runnable", runnable));174 const closure: *@This() = @alignCast(@fieldParentPtr("runnable", runnable));
180 @call(.auto, func, .{id.?} ++ closure.arguments);175 @call(.auto, func, .{id.?} ++ closure.arguments);
181 closure.wait_group.finish();176 closure.wait_group.finish();
182
183 // The thread pool's allocator is protected by the mutex.
184 const mutex = &closure.pool.mutex;
185 mutex.lock();
186 defer mutex.unlock();
187
188 closure.pool.allocator.destroy(closure);177 closure.pool.allocator.destroy(closure);
189 }178 }
190 };179 };
...@@ -228,12 +217,6 @@ pub fn spawn(pool: *Pool, comptime func: anytype, args: anytype) !void {...@@ -228,12 +217,6 @@ pub fn spawn(pool: *Pool, comptime func: anytype, args: anytype) !void {
228 fn runFn(runnable: *Runnable, _: ?usize) void {217 fn runFn(runnable: *Runnable, _: ?usize) void {
229 const closure: *@This() = @alignCast(@fieldParentPtr("runnable", runnable));218 const closure: *@This() = @alignCast(@fieldParentPtr("runnable", runnable));
230 @call(.auto, func, closure.arguments);219 @call(.auto, func, closure.arguments);
231
232 // The thread pool's allocator is protected by the mutex.
233 const mutex = &closure.pool.mutex;
234 mutex.lock();
235 defer mutex.unlock();
236
237 closure.pool.allocator.destroy(closure);220 closure.pool.allocator.destroy(closure);
238 }221 }
239 };222 };