authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2025-04-02 18:03:53-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-02 16:30:59-07:00
log6ba65ca972cf38a57266cf624932ae61d4cbba2d
treeab8273617df91c2261eed28fdb214f464cfa5f2f
parentb174777437ffc537f58e45cd54cc4f9bcf5305d9

Io.Condition: implement full API


3 files changed, 78 insertions(+), 24 deletions(-)

lib/std/Io.zig+18-7
......@@ -630,7 +630,7 @@ pub const VTable = struct {
630630 mutexUnlock: *const fn (?*anyopaque, prev_state: Mutex.State, mutex: *Mutex) void,
631631
632632 conditionWait: *const fn (?*anyopaque, cond: *Condition, mutex: *Mutex) Cancelable!void,
633 conditionWake: *const fn (?*anyopaque, cond: *Condition) void,
633 conditionWake: *const fn (?*anyopaque, cond: *Condition, wake: Condition.Wake) void,
634634
635635 createFile: *const fn (?*anyopaque, dir: fs.Dir, sub_path: []const u8, flags: fs.File.CreateFlags) FileOpenError!fs.File,
636636 openFile: *const fn (?*anyopaque, dir: fs.Dir, sub_path: []const u8, flags: fs.File.OpenFlags) FileOpenError!fs.File,
......@@ -809,9 +809,20 @@ pub const Condition = struct {
809809 return io.vtable.conditionWait(io.userdata, cond, mutex);
810810 }
811811
812 pub fn wake(cond: *Condition, io: Io) void {
813 io.vtable.conditionWake(io.userdata, cond);
812 pub fn signal(cond: *Condition, io: Io) void {
813 io.vtable.conditionWake(io.userdata, cond, .one);
814814 }
815
816 pub fn broadcast(cond: *Condition, io: Io) void {
817 io.vtable.conditionWake(io.userdata, cond, .all);
818 }
819
820 pub const Wake = enum {
821 /// wake up only one thread
822 one,
823 /// wake up all thread
824 all,
825 };
815826};
816827
817828pub const TypeErasedQueue = struct {
......@@ -863,7 +874,7 @@ pub const TypeErasedQueue = struct {
863874 remaining = remaining[copy_len..];
864875 getter.data.remaining = getter.data.remaining[copy_len..];
865876 if (getter.data.remaining.len == 0) {
866 getter.data.condition.wake(io);
877 getter.data.condition.signal(io);
867878 continue;
868879 }
869880 q.getters.prepend(getter);
......@@ -946,7 +957,7 @@ pub const TypeErasedQueue = struct {
946957 putter.data.remaining = putter.data.remaining[copy_len..];
947958 remaining = remaining[copy_len..];
948959 if (putter.data.remaining.len == 0) {
949 putter.data.condition.wake(io);
960 putter.data.condition.signal(io);
950961 } else {
951962 assert(remaining.len == 0);
952963 q.putters.prepend(putter);
......@@ -979,7 +990,7 @@ pub const TypeErasedQueue = struct {
979990 putter.data.remaining = putter.data.remaining[copy_len..];
980991 q.put_index += copy_len;
981992 if (putter.data.remaining.len == 0) {
982 putter.data.condition.wake(io);
993 putter.data.condition.signal(io);
983994 continue;
984995 }
985996 const second_available = q.buffer[0..q.get_index];
......@@ -988,7 +999,7 @@ pub const TypeErasedQueue = struct {
988999 putter.data.remaining = putter.data.remaining[copy_len..];
9891000 q.put_index = copy_len;
9901001 if (putter.data.remaining.len == 0) {
991 putter.data.condition.wake(io);
1002 putter.data.condition.signal(io);
9921003 continue;
9931004 }
9941005 q.putters.prepend(putter);
lib/std/Io/EventLoop.zig+55-15
......@@ -555,8 +555,24 @@ const SwitchMessage = struct {
555555 .condition_wait => |condition_wait| {
556556 const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev));
557557 assert(prev_fiber.queue_next == null);
558 const cond_state: *?*Fiber = @ptrCast(&condition_wait.cond.state);
559 assert(@atomicRmw(?*Fiber, cond_state, .Xchg, prev_fiber, .release) == null); // More than one wait on same Condition is illegal.
558 const cond_impl = prev_fiber.resultPointer(ConditionImpl);
559 cond_impl.* = .{
560 .tail = prev_fiber,
561 .event = .queued,
562 };
563 if (@cmpxchgStrong(
564 ?*Fiber,
565 @as(*?*Fiber, @ptrCast(&condition_wait.cond.state)),
566 null,
567 prev_fiber,
568 .release,
569 .acquire,
570 )) |waiting_fiber| {
571 const waiting_cond_impl = waiting_fiber.?.resultPointer(ConditionImpl);
572 assert(waiting_cond_impl.tail.queue_next == null);
573 waiting_cond_impl.tail.queue_next = prev_fiber;
574 waiting_cond_impl.tail = prev_fiber;
575 }
560576 condition_wait.mutex.unlock(el.io());
561577 },
562578 .exit => for (el.threads.allocated[0..@atomicLoad(u32, &el.threads.active, .acquire)]) |*each_thread| {
......@@ -1267,10 +1283,7 @@ fn sleep(userdata: ?*anyopaque, clockid: std.posix.clockid_t, deadline: Io.Deadl
12671283
12681284fn mutexLock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) error{Canceled}!void {
12691285 const el: *EventLoop = @alignCast(@ptrCast(userdata));
1270 el.yield(null, .{ .mutex_lock = .{
1271 .prev_state = prev_state,
1272 .mutex = mutex,
1273 } });
1286 el.yield(null, .{ .mutex_lock = .{ .prev_state = prev_state, .mutex = mutex } });
12741287}
12751288fn mutexUnlock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) void {
12761289 var maybe_waiting_fiber: ?*Fiber = @ptrFromInt(@intFromEnum(prev_state));
......@@ -1294,21 +1307,48 @@ fn mutexUnlock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mut
12941307 el.yield(maybe_waiting_fiber.?, .reschedule);
12951308}
12961309
1310const ConditionImpl = struct {
1311 tail: *Fiber,
1312 event: union(enum) {
1313 queued,
1314 wake: Io.Condition.Wake,
1315 },
1316};
1317
12971318fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) Io.Cancelable!void {
12981319 const el: *EventLoop = @alignCast(@ptrCast(userdata));
1299 el.yield(null, .{ .condition_wait = .{
1300 .cond = cond,
1301 .mutex = mutex,
1302 } });
1320 el.yield(null, .{ .condition_wait = .{ .cond = cond, .mutex = mutex } });
1321 const thread = Thread.current();
1322 const fiber = thread.currentFiber();
1323 const cond_impl = fiber.resultPointer(ConditionImpl);
13031324 try mutex.lock(el.io());
1325 switch (cond_impl.event) {
1326 .queued => {},
1327 .wake => |wake| if (fiber.queue_next) |next_fiber| switch (wake) {
1328 .one => if (@cmpxchgStrong(
1329 ?*Fiber,
1330 @as(*?*Fiber, @ptrCast(&cond.state)),
1331 null,
1332 next_fiber,
1333 .release,
1334 .acquire,
1335 )) |old_fiber| {
1336 const old_cond_impl = old_fiber.?.resultPointer(ConditionImpl);
1337 assert(old_cond_impl.tail.queue_next == null);
1338 old_cond_impl.tail.queue_next = next_fiber;
1339 old_cond_impl.tail = cond_impl.tail;
1340 },
1341 .all => el.schedule(thread, .{ .head = next_fiber, .tail = cond_impl.tail }),
1342 },
1343 }
1344 fiber.queue_next = null;
13041345}
13051346
1306fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition) void {
1347fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.Wake) void {
13071348 const el: *EventLoop = @alignCast(@ptrCast(userdata));
1308 const cond_state: *?*Fiber = @ptrCast(&cond.state);
1309 if (@atomicRmw(?*Fiber, cond_state, .Xchg, null, .acquire)) |fiber| {
1310 el.yield(fiber, .reschedule);
1311 }
1349 const waiting_fiber = @atomicRmw(?*Fiber, @as(*?*Fiber, @ptrCast(&cond.state)), .Xchg, null, .acquire) orelse return;
1350 waiting_fiber.resultPointer(ConditionImpl).event = .{ .wake = wake };
1351 el.yield(waiting_fiber, .reschedule);
13121352}
13131353
13141354fn errno(signed: i32) std.os.linux.E {
lib/std/Thread/Pool.zig+5-2
......@@ -666,7 +666,7 @@ fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) I
666666 }
667667}
668668
669fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition) void {
669fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.Wake) void {
670670 const pool: *std.Thread.Pool = @alignCast(@ptrCast(userdata));
671671 _ = pool;
672672 comptime assert(@TypeOf(cond.state) == u64);
......@@ -690,7 +690,10 @@ fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition) void {
690690 return;
691691 }
692692
693 const to_wake = 1;
693 const to_wake = switch (wake) {
694 .one => 1,
695 .all => wakeable,
696 };
694697
695698 // Reserve the amount of waiters to wake by incrementing the signals count.
696699 // Release barrier ensures code before the wake() happens before the signal it posted and consumed by the wait() threads.