authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-04-03 17:54:37-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-02 16:30:59-07:00
log4a3ef0f779ab7a18a5968537f2cec573aff11554
tree9650d87e4c660ba0fd78d137e1052c39efcab0a2
parent6ba65ca972cf38a57266cf624932ae61d4cbba2d

introduce Io.select and implement it in thread pool


2 files changed, 228 insertions(+), 78 deletions(-)

lib/std/Io.zig+155-60
...@@ -626,17 +626,21 @@ pub const VTable = struct {...@@ -626,17 +626,21 @@ pub const VTable = struct {
626 /// Thread-safe.626 /// Thread-safe.
627 cancelRequested: *const fn (?*anyopaque) bool,627 cancelRequested: *const fn (?*anyopaque) bool,
628628
629 /// Blocks until one of the futures from the list has a result ready, such
630 /// that awaiting it will not block. Returns that index.
631 select: *const fn (?*anyopaque, futures: []const *AnyFuture) usize,
632
629 mutexLock: *const fn (?*anyopaque, prev_state: Mutex.State, mutex: *Mutex) Cancelable!void,633 mutexLock: *const fn (?*anyopaque, prev_state: Mutex.State, mutex: *Mutex) Cancelable!void,
630 mutexUnlock: *const fn (?*anyopaque, prev_state: Mutex.State, mutex: *Mutex) void,634 mutexUnlock: *const fn (?*anyopaque, prev_state: Mutex.State, mutex: *Mutex) void,
631635
632 conditionWait: *const fn (?*anyopaque, cond: *Condition, mutex: *Mutex) Cancelable!void,636 conditionWait: *const fn (?*anyopaque, cond: *Condition, mutex: *Mutex) Cancelable!void,
633 conditionWake: *const fn (?*anyopaque, cond: *Condition, wake: Condition.Wake) void,637 conditionWake: *const fn (?*anyopaque, cond: *Condition, wake: Condition.Wake) void,
634638
635 createFile: *const fn (?*anyopaque, dir: fs.Dir, sub_path: []const u8, flags: fs.File.CreateFlags) FileOpenError!fs.File,639 createFile: *const fn (?*anyopaque, dir: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File,
636 openFile: *const fn (?*anyopaque, dir: fs.Dir, sub_path: []const u8, flags: fs.File.OpenFlags) FileOpenError!fs.File,640 openFile: *const fn (?*anyopaque, dir: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File,
637 closeFile: *const fn (?*anyopaque, fs.File) void,641 closeFile: *const fn (?*anyopaque, File) void,
638 pread: *const fn (?*anyopaque, file: fs.File, buffer: []u8, offset: std.posix.off_t) FilePReadError!usize,642 pread: *const fn (?*anyopaque, file: File, buffer: []u8, offset: std.posix.off_t) File.PReadError!usize,
639 pwrite: *const fn (?*anyopaque, file: fs.File, buffer: []const u8, offset: std.posix.off_t) FilePWriteError!usize,643 pwrite: *const fn (?*anyopaque, file: File, buffer: []const u8, offset: std.posix.off_t) File.PWriteError!usize,
640644
641 now: *const fn (?*anyopaque, clockid: std.posix.clockid_t) ClockGetTimeError!Timestamp,645 now: *const fn (?*anyopaque, clockid: std.posix.clockid_t) ClockGetTimeError!Timestamp,
642 sleep: *const fn (?*anyopaque, clockid: std.posix.clockid_t, deadline: Deadline) SleepError!void,646 sleep: *const fn (?*anyopaque, clockid: std.posix.clockid_t, deadline: Deadline) SleepError!void,
...@@ -647,28 +651,118 @@ pub const Cancelable = error{...@@ -647,28 +651,118 @@ pub const Cancelable = error{
647 Canceled,651 Canceled,
648};652};
649653
650pub const OpenFlags = fs.File.OpenFlags;654pub const Dir = struct {
651pub const CreateFlags = fs.File.CreateFlags;655 handle: Handle,
656
657 pub fn cwd() Dir {
658 return .{ .handle = std.fs.cwd().fd };
659 }
660
661 pub const Handle = std.posix.fd_t;
662
663 pub fn openFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
664 return io.vtable.openFile(io.userdata, dir, sub_path, flags);
665 }
666
667 pub fn createFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
668 return io.vtable.createFile(io.userdata, dir, sub_path, flags);
669 }
670
671 pub const WriteFileOptions = struct {
672 /// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
673 /// On WASI, `sub_path` should be encoded as valid UTF-8.
674 /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
675 sub_path: []const u8,
676 data: []const u8,
677 flags: File.CreateFlags = .{},
678 };
679
680 pub const WriteFileError = File.WriteError || File.OpenError || Cancelable;
681
682 /// Writes content to the file system, using the file creation flags provided.
683 pub fn writeFile(dir: Dir, io: Io, options: WriteFileOptions) WriteFileError!void {
684 var file = try dir.createFile(io, options.sub_path, options.flags);
685 defer file.close(io);
686 try file.writeAll(io, options.data);
687 }
688};
689
690pub const File = struct {
691 handle: Handle,
692
693 pub const Handle = std.posix.fd_t;
694
695 pub const OpenFlags = fs.File.OpenFlags;
696 pub const CreateFlags = fs.File.CreateFlags;
697
698 pub const OpenError = fs.File.OpenError || Cancelable;
699
700 pub fn close(file: File, io: Io) void {
701 return io.vtable.closeFile(io.userdata, file);
702 }
703
704 pub const ReadError = fs.File.ReadError || Cancelable;
705
706 pub fn read(file: File, io: Io, buffer: []u8) ReadError!usize {
707 return @errorCast(file.pread(io, buffer, -1));
708 }
709
710 pub const PReadError = fs.File.PReadError || Cancelable;
711
712 pub fn pread(file: File, io: Io, buffer: []u8, offset: std.posix.off_t) PReadError!usize {
713 return io.vtable.pread(io.userdata, file, buffer, offset);
714 }
715
716 pub const WriteError = fs.File.WriteError || Cancelable;
717
718 pub fn write(file: File, io: Io, buffer: []const u8) WriteError!usize {
719 return @errorCast(file.pwrite(io, buffer, -1));
720 }
721
722 pub const PWriteError = fs.File.PWriteError || Cancelable;
723
724 pub fn pwrite(file: File, io: Io, buffer: []const u8, offset: std.posix.off_t) PWriteError!usize {
725 return io.vtable.pwrite(io.userdata, file, buffer, offset);
726 }
727
728 pub fn writeAll(file: File, io: Io, bytes: []const u8) WriteError!void {
729 var index: usize = 0;
730 while (index < bytes.len) {
731 index += try file.write(io, bytes[index..]);
732 }
733 }
652734
653pub const FileOpenError = fs.File.OpenError || Cancelable;735 pub fn readAll(file: File, io: Io, buffer: []u8) ReadError!usize {
654pub const FileReadError = fs.File.ReadError || Cancelable;736 var index: usize = 0;
655pub const FilePReadError = fs.File.PReadError || Cancelable;737 while (index != buffer.len) {
656pub const FileWriteError = fs.File.WriteError || Cancelable;738 const amt = try file.read(io, buffer[index..]);
657pub const FilePWriteError = fs.File.PWriteError || Cancelable;739 if (amt == 0) break;
740 index += amt;
741 }
742 return index;
743 }
744};
658745
659pub const Timestamp = enum(i96) {746pub const Timestamp = enum(i96) {
660 _,747 _,
661748
662 pub fn durationTo(from: Timestamp, to: Timestamp) i96 {749 pub fn durationTo(from: Timestamp, to: Timestamp) Duration {
663 return @intFromEnum(to) - @intFromEnum(from);750 return .{ .nanoseconds = @intFromEnum(to) - @intFromEnum(from) };
664 }751 }
665752
666 pub fn addDuration(from: Timestamp, duration: i96) Timestamp {753 pub fn addDuration(from: Timestamp, duration: Duration) Timestamp {
667 return @enumFromInt(@intFromEnum(from) + duration);754 return @enumFromInt(@intFromEnum(from) + duration.nanoseconds);
668 }755 }
669};756};
670pub const Deadline = union(enum) {757pub const Duration = struct {
671 nanoseconds: i96,758 nanoseconds: i96,
759
760 pub fn ms(x: u64) Duration {
761 return .{ .nanoseconds = @as(i96, x) * std.time.ns_per_ms };
762 }
763};
764pub const Deadline = union(enum) {
765 duration: Duration,
672 timestamp: Timestamp,766 timestamp: Timestamp,
673};767};
674pub const ClockGetTimeError = std.posix.ClockGetTimeError || Cancelable;768pub const ClockGetTimeError = std.posix.ClockGetTimeError || Cancelable;
...@@ -1055,7 +1149,7 @@ pub fn Queue(Elem: type) type {...@@ -1055,7 +1149,7 @@ pub fn Queue(Elem: type) type {
10551149
1056/// Calls `function` with `args`, such that the return value of the function is1150/// Calls `function` with `args`, such that the return value of the function is
1057/// not guaranteed to be available until `await` is called.1151/// not guaranteed to be available until `await` is called.
1058pub fn async(io: Io, function: anytype, args: anytype) Future(@typeInfo(@TypeOf(function)).@"fn".return_type.?) {1152pub fn async(io: Io, function: anytype, args: std.meta.ArgsTuple(@TypeOf(function))) Future(@typeInfo(@TypeOf(function)).@"fn".return_type.?) {
1059 const Result = @typeInfo(@TypeOf(function)).@"fn".return_type.?;1153 const Result = @typeInfo(@TypeOf(function)).@"fn".return_type.?;
1060 const Args = @TypeOf(args);1154 const Args = @TypeOf(args);
1061 const TypeErased = struct {1155 const TypeErased = struct {
...@@ -1079,7 +1173,7 @@ pub fn async(io: Io, function: anytype, args: anytype) Future(@typeInfo(@TypeOf(...@@ -1079,7 +1173,7 @@ pub fn async(io: Io, function: anytype, args: anytype) Future(@typeInfo(@TypeOf(
10791173
1080/// Calls `function` with `args` asynchronously. The resource cleans itself up1174/// Calls `function` with `args` asynchronously. The resource cleans itself up
1081/// when the function returns. Does not support await, cancel, or a return value.1175/// when the function returns. Does not support await, cancel, or a return value.
1082pub fn go(io: Io, function: anytype, args: anytype) void {1176pub fn go(io: Io, function: anytype, args: std.meta.ArgsTuple(@TypeOf(function))) void {
1083 const Args = @TypeOf(args);1177 const Args = @TypeOf(args);
1084 const TypeErased = struct {1178 const TypeErased = struct {
1085 fn start(context: *const anyopaque) void {1179 fn start(context: *const anyopaque) void {
...@@ -1095,55 +1189,56 @@ pub fn go(io: Io, function: anytype, args: anytype) void {...@@ -1095,55 +1189,56 @@ pub fn go(io: Io, function: anytype, args: anytype) void {
1095 );1189 );
1096}1190}
10971191
1098pub fn openFile(io: Io, dir: fs.Dir, sub_path: []const u8, flags: fs.File.OpenFlags) FileOpenError!fs.File {1192pub fn now(io: Io, clockid: std.posix.clockid_t) ClockGetTimeError!Timestamp {
1099 return io.vtable.openFile(io.userdata, dir, sub_path, flags);1193 return io.vtable.now(io.userdata, clockid);
1100}
1101
1102pub fn createFile(io: Io, dir: fs.Dir, sub_path: []const u8, flags: fs.File.CreateFlags) FileOpenError!fs.File {
1103 return io.vtable.createFile(io.userdata, dir, sub_path, flags);
1104}
1105
1106pub fn closeFile(io: Io, file: fs.File) void {
1107 return io.vtable.closeFile(io.userdata, file);
1108}
1109
1110pub fn read(io: Io, file: fs.File, buffer: []u8) FileReadError!usize {
1111 return @errorCast(io.pread(file, buffer, -1));
1112}
1113
1114pub fn pread(io: Io, file: fs.File, buffer: []u8, offset: std.posix.off_t) FilePReadError!usize {
1115 return io.vtable.pread(io.userdata, file, buffer, offset);
1116}1194}
11171195
1118pub fn write(io: Io, file: fs.File, buffer: []const u8) FileWriteError!usize {1196pub fn sleep(io: Io, clockid: std.posix.clockid_t, deadline: Deadline) SleepError!void {
1119 return @errorCast(io.pwrite(file, buffer, -1));1197 return io.vtable.sleep(io.userdata, clockid, deadline);
1120}1198}
11211199
1122pub fn pwrite(io: Io, file: fs.File, buffer: []const u8, offset: std.posix.off_t) FilePWriteError!usize {1200pub fn sleepDuration(io: Io, duration: Duration) SleepError!void {
1123 return io.vtable.pwrite(io.userdata, file, buffer, offset);1201 return io.vtable.sleep(io.userdata, .MONOTONIC, .{ .duration = duration });
1124}1202}
11251203
1126pub fn writeAll(io: Io, file: fs.File, bytes: []const u8) FileWriteError!void {1204/// Given a struct with each field a `*Future`, returns a union with the same
1127 var index: usize = 0;1205/// fields, each field type the future's result.
1128 while (index < bytes.len) {1206pub fn SelectUnion(S: type) type {
1129 index += try io.write(file, bytes[index..]);1207 const struct_fields = @typeInfo(S).@"struct".fields;
1208 var fields: [struct_fields.len]std.builtin.Type.UnionField = undefined;
1209 for (&fields, struct_fields) |*union_field, struct_field| {
1210 const F = @typeInfo(struct_field.type).pointer.child;
1211 const Result = @TypeOf(@as(F, undefined).result);
1212 union_field.* = .{
1213 .name = struct_field.name,
1214 .type = Result,
1215 .alignment = struct_field.alignment,
1216 };
1130 }1217 }
1218 return @Type(.{ .@"union" = .{
1219 .layout = .auto,
1220 .tag_type = std.meta.FieldEnum(S),
1221 .fields = &fields,
1222 .decls = &.{},
1223 } });
1131}1224}
11321225
1133pub fn readAll(io: Io, file: fs.File, buffer: []u8) FileReadError!usize {1226/// `s` is a struct with every field a `*Future(T)`, where `T` can be any type,
1134 var index: usize = 0;1227/// and can be different for each field.
1135 while (index != buffer.len) {1228pub fn select(io: Io, s: anytype) SelectUnion(@TypeOf(s)) {
1136 const amt = try io.read(file, buffer[index..]);1229 const U = SelectUnion(@TypeOf(s));
1137 if (amt == 0) break;1230 const S = @TypeOf(s);
1138 index += amt;1231 const fields = @typeInfo(S).@"struct".fields;
1232 var futures: [fields.len]*AnyFuture = undefined;
1233 inline for (fields, &futures) |field, *any_future| {
1234 const future = @field(s, field.name);
1235 any_future.* = future.any_future orelse return @unionInit(U, field.name, future.result);
1236 }
1237 switch (io.vtable.select(io.userdata, &futures)) {
1238 inline 0...(fields.len - 1) => |selected_index| {
1239 const field_name = fields[selected_index].name;
1240 return @unionInit(U, field_name, @field(s, field_name).await(io));
1241 },
1242 else => unreachable,
1139 }1243 }
1140 return index;
1141}
1142
1143pub fn now(io: Io, clockid: std.posix.clockid_t) ClockGetTimeError!Timestamp {
1144 return io.vtable.now(io.userdata, clockid);
1145}
1146
1147pub fn sleep(io: Io, clockid: std.posix.clockid_t, deadline: Deadline) SleepError!void {
1148 return io.vtable.sleep(io.userdata, clockid, deadline);
1149}1244}
lib/std/Thread/Pool.zig+73-18
...@@ -335,6 +335,7 @@ pub fn io(pool: *Pool) Io {...@@ -335,6 +335,7 @@ pub fn io(pool: *Pool) Io {
335 .go = go,335 .go = go,
336 .cancel = cancel,336 .cancel = cancel,
337 .cancelRequested = cancelRequested,337 .cancelRequested = cancelRequested,
338 .select = select,
338339
339 .mutexLock = mutexLock,340 .mutexLock = mutexLock,
340 .mutexUnlock = mutexUnlock,341 .mutexUnlock = mutexUnlock,
...@@ -358,10 +359,13 @@ const AsyncClosure = struct {...@@ -358,10 +359,13 @@ const AsyncClosure = struct {
358 func: *const fn (context: *anyopaque, result: *anyopaque) void,359 func: *const fn (context: *anyopaque, result: *anyopaque) void,
359 runnable: Runnable = .{ .runFn = runFn },360 runnable: Runnable = .{ .runFn = runFn },
360 reset_event: std.Thread.ResetEvent,361 reset_event: std.Thread.ResetEvent,
362 select_condition: ?*std.Thread.ResetEvent,
361 cancel_tid: std.Thread.Id,363 cancel_tid: std.Thread.Id,
362 context_offset: usize,364 context_offset: usize,
363 result_offset: usize,365 result_offset: usize,
364366
367 const done_reset_event: *std.Thread.ResetEvent = @ptrFromInt(std.mem.alignBackward(usize, std.math.maxInt(usize), @alignOf(std.Thread.ResetEvent)));
368
365 const canceling_tid: std.Thread.Id = switch (@typeInfo(std.Thread.Id)) {369 const canceling_tid: std.Thread.Id = switch (@typeInfo(std.Thread.Id)) {
366 .int => |int_info| switch (int_info.signedness) {370 .int => |int_info| switch (int_info.signedness) {
367 .signed => -1,371 .signed => -1,
...@@ -396,6 +400,17 @@ const AsyncClosure = struct {...@@ -396,6 +400,17 @@ const AsyncClosure = struct {
396 .acq_rel,400 .acq_rel,
397 .acquire,401 .acquire,
398 )) |cancel_tid| assert(cancel_tid == canceling_tid);402 )) |cancel_tid| assert(cancel_tid == canceling_tid);
403
404 if (@atomicRmw(
405 ?*std.Thread.ResetEvent,
406 &closure.select_condition,
407 .Xchg,
408 done_reset_event,
409 .release,
410 )) |select_reset| {
411 assert(select_reset != done_reset_event);
412 select_reset.set();
413 }
399 closure.reset_event.set();414 closure.reset_event.set();
400 }415 }
401416
...@@ -455,6 +470,7 @@ fn @"async"(...@@ -455,6 +470,7 @@ fn @"async"(
455 .result_offset = result_offset,470 .result_offset = result_offset,
456 .reset_event = .{},471 .reset_event = .{},
457 .cancel_tid = 0,472 .cancel_tid = 0,
473 .select_condition = null,
458 };474 };
459 @memcpy(closure.contextPointer()[0..context.len], context);475 @memcpy(closure.contextPointer()[0..context.len], context);
460 pool.run_queue.prepend(&closure.runnable.node);476 pool.run_queue.prepend(&closure.runnable.node);
...@@ -720,47 +736,54 @@ fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition....@@ -720,47 +736,54 @@ fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.
720736
721fn createFile(737fn createFile(
722 userdata: ?*anyopaque,738 userdata: ?*anyopaque,
723 dir: std.fs.Dir,739 dir: Io.Dir,
724 sub_path: []const u8,740 sub_path: []const u8,
725 flags: std.fs.File.CreateFlags,741 flags: Io.File.CreateFlags,
726) Io.FileOpenError!std.fs.File {742) Io.File.OpenError!Io.File {
727 const pool: *std.Thread.Pool = @alignCast(@ptrCast(userdata));743 const pool: *std.Thread.Pool = @alignCast(@ptrCast(userdata));
728 try pool.checkCancel();744 try pool.checkCancel();
729 return dir.createFile(sub_path, flags);745 const fs_dir: std.fs.Dir = .{ .fd = dir.handle };
746 const fs_file = try fs_dir.createFile(sub_path, flags);
747 return .{ .handle = fs_file.handle };
730}748}
731749
732fn openFile(750fn openFile(
733 userdata: ?*anyopaque,751 userdata: ?*anyopaque,
734 dir: std.fs.Dir,752 dir: Io.Dir,
735 sub_path: []const u8,753 sub_path: []const u8,
736 flags: std.fs.File.OpenFlags,754 flags: Io.File.OpenFlags,
737) Io.FileOpenError!std.fs.File {755) Io.File.OpenError!Io.File {
738 const pool: *std.Thread.Pool = @alignCast(@ptrCast(userdata));756 const pool: *std.Thread.Pool = @alignCast(@ptrCast(userdata));
739 try pool.checkCancel();757 try pool.checkCancel();
740 return dir.openFile(sub_path, flags);758 const fs_dir: std.fs.Dir = .{ .fd = dir.handle };
759 const fs_file = try fs_dir.openFile(sub_path, flags);
760 return .{ .handle = fs_file.handle };
741}761}
742762
743fn closeFile(userdata: ?*anyopaque, file: std.fs.File) void {763fn closeFile(userdata: ?*anyopaque, file: Io.File) void {
744 const pool: *std.Thread.Pool = @alignCast(@ptrCast(userdata));764 const pool: *std.Thread.Pool = @alignCast(@ptrCast(userdata));
745 _ = pool;765 _ = pool;
746 return file.close();766 const fs_file: std.fs.File = .{ .handle = file.handle };
767 return fs_file.close();
747}768}
748769
749fn pread(userdata: ?*anyopaque, file: std.fs.File, buffer: []u8, offset: std.posix.off_t) Io.FilePReadError!usize {770fn pread(userdata: ?*anyopaque, file: Io.File, buffer: []u8, offset: std.posix.off_t) Io.File.PReadError!usize {
750 const pool: *std.Thread.Pool = @alignCast(@ptrCast(userdata));771 const pool: *std.Thread.Pool = @alignCast(@ptrCast(userdata));
751 try pool.checkCancel();772 try pool.checkCancel();
773 const fs_file: std.fs.File = .{ .handle = file.handle };
752 return switch (offset) {774 return switch (offset) {
753 -1 => file.read(buffer),775 -1 => fs_file.read(buffer),
754 else => file.pread(buffer, @bitCast(offset)),776 else => fs_file.pread(buffer, @bitCast(offset)),
755 };777 };
756}778}
757779
758fn pwrite(userdata: ?*anyopaque, file: std.fs.File, buffer: []const u8, offset: std.posix.off_t) Io.FilePWriteError!usize {780fn pwrite(userdata: ?*anyopaque, file: Io.File, buffer: []const u8, offset: std.posix.off_t) Io.File.PWriteError!usize {
759 const pool: *std.Thread.Pool = @alignCast(@ptrCast(userdata));781 const pool: *std.Thread.Pool = @alignCast(@ptrCast(userdata));
760 try pool.checkCancel();782 try pool.checkCancel();
783 const fs_file: std.fs.File = .{ .handle = file.handle };
761 return switch (offset) {784 return switch (offset) {
762 -1 => file.write(buffer),785 -1 => fs_file.write(buffer),
763 else => file.pwrite(buffer, @bitCast(offset)),786 else => fs_file.pwrite(buffer, @bitCast(offset)),
764 };787 };
765}788}
766789
...@@ -774,7 +797,7 @@ fn now(userdata: ?*anyopaque, clockid: std.posix.clockid_t) Io.ClockGetTimeError...@@ -774,7 +797,7 @@ fn now(userdata: ?*anyopaque, clockid: std.posix.clockid_t) Io.ClockGetTimeError
774fn sleep(userdata: ?*anyopaque, clockid: std.posix.clockid_t, deadline: Io.Deadline) Io.SleepError!void {797fn sleep(userdata: ?*anyopaque, clockid: std.posix.clockid_t, deadline: Io.Deadline) Io.SleepError!void {
775 const pool: *std.Thread.Pool = @alignCast(@ptrCast(userdata));798 const pool: *std.Thread.Pool = @alignCast(@ptrCast(userdata));
776 const deadline_nanoseconds: i96 = switch (deadline) {799 const deadline_nanoseconds: i96 = switch (deadline) {
777 .nanoseconds => |nanoseconds| nanoseconds,800 .duration => |duration| duration.nanoseconds,
778 .timestamp => |timestamp| @intFromEnum(timestamp),801 .timestamp => |timestamp| @intFromEnum(timestamp),
779 };802 };
780 var timespec: std.posix.timespec = .{803 var timespec: std.posix.timespec = .{
...@@ -784,7 +807,7 @@ fn sleep(userdata: ?*anyopaque, clockid: std.posix.clockid_t, deadline: Io.Deadl...@@ -784,7 +807,7 @@ fn sleep(userdata: ?*anyopaque, clockid: std.posix.clockid_t, deadline: Io.Deadl
784 while (true) {807 while (true) {
785 try pool.checkCancel();808 try pool.checkCancel();
786 switch (std.os.linux.E.init(std.os.linux.clock_nanosleep(clockid, .{ .ABSTIME = switch (deadline) {809 switch (std.os.linux.E.init(std.os.linux.clock_nanosleep(clockid, .{ .ABSTIME = switch (deadline) {
787 .nanoseconds => false,810 .duration => false,
788 .timestamp => true,811 .timestamp => true,
789 } }, &timespec, &timespec))) {812 } }, &timespec, &timespec))) {
790 .SUCCESS => return,813 .SUCCESS => return,
...@@ -795,3 +818,35 @@ fn sleep(userdata: ?*anyopaque, clockid: std.posix.clockid_t, deadline: Io.Deadl...@@ -795,3 +818,35 @@ fn sleep(userdata: ?*anyopaque, clockid: std.posix.clockid_t, deadline: Io.Deadl
795 }818 }
796 }819 }
797}820}
821
822fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize {
823 const pool: *std.Thread.Pool = @alignCast(@ptrCast(userdata));
824 _ = pool;
825
826 var reset_event: std.Thread.ResetEvent = .{};
827
828 for (futures, 0..) |future, i| {
829 const closure: *AsyncClosure = @ptrCast(@alignCast(future));
830 if (@atomicRmw(?*std.Thread.ResetEvent, &closure.select_condition, .Xchg, &reset_event, .seq_cst) == AsyncClosure.done_reset_event) {
831 for (futures[0..i]) |cleanup_future| {
832 const cleanup_closure: *AsyncClosure = @ptrCast(@alignCast(cleanup_future));
833 if (@atomicRmw(?*std.Thread.ResetEvent, &cleanup_closure.select_condition, .Xchg, null, .seq_cst) == AsyncClosure.done_reset_event) {
834 cleanup_closure.reset_event.wait(); // Ensure no reference to our stack-allocated reset_event.
835 }
836 }
837 return i;
838 }
839 }
840
841 reset_event.wait();
842
843 var result: ?usize = null;
844 for (futures, 0..) |future, i| {
845 const closure: *AsyncClosure = @ptrCast(@alignCast(future));
846 if (@atomicRmw(?*std.Thread.ResetEvent, &closure.select_condition, .Xchg, null, .seq_cst) == AsyncClosure.done_reset_event) {
847 closure.reset_event.wait(); // Ensure no reference to our stack-allocated reset_event.
848 if (result == null) result = i; // In case multiple are ready, return first.
849 }
850 }
851 return result.?;
852}