| ... | @@ -13,7 +13,6 @@ const IpAddress = std.Io.net.IpAddress; | ... | @@ -13,7 +13,6 @@ const IpAddress = std.Io.net.IpAddress; |
| 13 | const Allocator = std.mem.Allocator; | 13 | const Allocator = std.mem.Allocator; |
| 14 | const assert = std.debug.assert; | 14 | const assert = std.debug.assert; |
| 15 | const posix = std.posix; | 15 | const posix = std.posix; |
| 16 | const ResetEvent = std.Thread.ResetEvent; | | |
| 17 | | 16 | |
| 18 | /// Thread-safe. | 17 | /// Thread-safe. |
| 19 | allocator: Allocator, | 18 | allocator: Allocator, |
| ... | @@ -153,8 +152,10 @@ pub fn io(t: *Threaded) Io { | ... | @@ -153,8 +152,10 @@ pub fn io(t: *Threaded) Io { |
| 153 | .cancel = cancel, | 152 | .cancel = cancel, |
| 154 | .cancelRequested = cancelRequested, | 153 | .cancelRequested = cancelRequested, |
| 155 | .select = select, | 154 | .select = select, |
| | 155 | |
| 156 | .groupAsync = groupAsync, | 156 | .groupAsync = groupAsync, |
| 157 | .groupWait = groupWait, | 157 | .groupWait = groupWait, |
| | 158 | .groupWaitUncancelable = groupWaitUncancelable, |
| 158 | .groupCancel = groupCancel, | 159 | .groupCancel = groupCancel, |
| 159 | | 160 | |
| 160 | .mutexLock = mutexLock, | 161 | .mutexLock = mutexLock, |
| ... | @@ -300,7 +301,7 @@ const AsyncClosure = struct { | ... | @@ -300,7 +301,7 @@ const AsyncClosure = struct { |
| 300 | } | 301 | } |
| 301 | | 302 | |
| 302 | fn waitAndFree(ac: *AsyncClosure, gpa: Allocator, result: []u8) void { | 303 | fn waitAndFree(ac: *AsyncClosure, gpa: Allocator, result: []u8) void { |
| 303 | ac.reset_event.wait(); | 304 | ac.reset_event.waitUncancelable(); |
| 304 | @memcpy(result, ac.resultPointer()[0..result.len]); | 305 | @memcpy(result, ac.resultPointer()[0..result.len]); |
| 305 | free(ac, gpa, result.len); | 306 | free(ac, gpa, result.len); |
| 306 | } | 307 | } |
| ... | @@ -472,7 +473,7 @@ const GroupClosure = struct { | ... | @@ -472,7 +473,7 @@ const GroupClosure = struct { |
| 472 | assert(cancel_tid == Closure.canceling_tid); | 473 | assert(cancel_tid == Closure.canceling_tid); |
| 473 | // We already know the task is canceled before running the callback. Since all closures | 474 | // We already know the task is canceled before running the callback. Since all closures |
| 474 | // in a Group have void return type, we can return early. | 475 | // in a Group have void return type, we can return early. |
| 475 | std.Thread.WaitGroup.finishStateless(group_state, reset_event); | 476 | syncFinish(group_state, reset_event); |
| 476 | return; | 477 | return; |
| 477 | } | 478 | } |
| 478 | current_closure = closure; | 479 | current_closure = closure; |
| ... | @@ -485,7 +486,7 @@ const GroupClosure = struct { | ... | @@ -485,7 +486,7 @@ const GroupClosure = struct { |
| 485 | assert(cancel_tid == Closure.canceling_tid); | 486 | assert(cancel_tid == Closure.canceling_tid); |
| 486 | } | 487 | } |
| 487 | | 488 | |
| 488 | std.Thread.WaitGroup.finishStateless(group_state, reset_event); | 489 | syncFinish(group_state, reset_event); |
| 489 | } | 490 | } |
| 490 | | 491 | |
| 491 | fn free(gc: *GroupClosure, gpa: Allocator) void { | 492 | fn free(gc: *GroupClosure, gpa: Allocator) void { |
| ... | @@ -505,6 +506,32 @@ const GroupClosure = struct { | ... | @@ -505,6 +506,32 @@ const GroupClosure = struct { |
| 505 | const base: [*]u8 = @ptrCast(gc); | 506 | const base: [*]u8 = @ptrCast(gc); |
| 506 | return base + contextOffset(gc.context_alignment); | 507 | return base + contextOffset(gc.context_alignment); |
| 507 | } | 508 | } |
| | 509 | |
| | 510 | const sync_is_waiting: usize = 1 << 0; |
| | 511 | const sync_one_pending: usize = 1 << 1; |
| | 512 | |
| | 513 | fn syncStart(state: *std.atomic.Value(usize)) void { |
| | 514 | const prev_state = state.fetchAdd(sync_one_pending, .monotonic); |
| | 515 | assert((prev_state / sync_one_pending) < (std.math.maxInt(usize) / sync_one_pending)); |
| | 516 | } |
| | 517 | |
| | 518 | fn syncFinish(state: *std.atomic.Value(usize), event: *ResetEvent) void { |
| | 519 | const prev_state = state.fetchSub(sync_one_pending, .acq_rel); |
| | 520 | assert((prev_state / sync_one_pending) > 0); |
| | 521 | if (prev_state == (sync_one_pending | sync_is_waiting)) event.set(); |
| | 522 | } |
| | 523 | |
| | 524 | fn syncWait(t: *Threaded, state: *std.atomic.Value(usize), event: *ResetEvent) Io.Cancelable!void { |
| | 525 | const prev_state = state.fetchAdd(sync_is_waiting, .acquire); |
| | 526 | assert(prev_state & sync_is_waiting == 0); |
| | 527 | if ((prev_state / sync_one_pending) > 0) try event.wait(t); |
| | 528 | } |
| | 529 | |
| | 530 | fn syncWaitUncancelable(state: *std.atomic.Value(usize), event: *ResetEvent) void { |
| | 531 | const prev_state = state.fetchAdd(sync_is_waiting, .acquire); |
| | 532 | assert(prev_state & sync_is_waiting == 0); |
| | 533 | if ((prev_state / sync_one_pending) > 0) event.waitUncancelable(); |
| | 534 | } |
| 508 | }; | 535 | }; |
| 509 | | 536 | |
| 510 | fn groupAsync( | 537 | fn groupAsync( |
| ... | @@ -566,22 +593,40 @@ fn groupAsync( | ... | @@ -566,22 +593,40 @@ fn groupAsync( |
| 566 | // This needs to be done before unlocking the mutex to avoid a race with | 593 | // This needs to be done before unlocking the mutex to avoid a race with |
| 567 | // the associated task finishing. | 594 | // the associated task finishing. |
| 568 | const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state); | 595 | const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state); |
| 569 | std.Thread.WaitGroup.startStateless(group_state); | 596 | GroupClosure.syncStart(group_state); |
| 570 | | 597 | |
| 571 | t.mutex.unlock(); | 598 | t.mutex.unlock(); |
| 572 | t.cond.signal(); | 599 | t.cond.signal(); |
| 573 | } | 600 | } |
| 574 | | 601 | |
| 575 | fn groupWait(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void { | 602 | fn groupWait(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) Io.Cancelable!void { |
| 576 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 603 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 577 | const gpa = t.allocator; | 604 | const gpa = t.allocator; |
| 578 | | 605 | |
| 579 | if (builtin.single_threaded) return; | 606 | if (builtin.single_threaded) return; |
| 580 | | 607 | |
| 581 | // TODO these primitives are too high level, need to check cancel on EINTR | | |
| 582 | const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state); | 608 | const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state); |
| 583 | const reset_event: *ResetEvent = @ptrCast(&group.context); | 609 | const reset_event: *ResetEvent = @ptrCast(&group.context); |
| 584 | std.Thread.WaitGroup.waitStateless(group_state, reset_event); | 610 | try GroupClosure.syncWait(t, group_state, reset_event); |
| | 611 | |
| | 612 | var node: *std.SinglyLinkedList.Node = @ptrCast(@alignCast(token)); |
| | 613 | while (true) { |
| | 614 | const gc: *GroupClosure = @fieldParentPtr("node", node); |
| | 615 | const node_next = node.next; |
| | 616 | gc.free(gpa); |
| | 617 | node = node_next orelse break; |
| | 618 | } |
| | 619 | } |
| | 620 | |
| | 621 | fn groupWaitUncancelable(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void { |
| | 622 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| | 623 | const gpa = t.allocator; |
| | 624 | |
| | 625 | if (builtin.single_threaded) return; |
| | 626 | |
| | 627 | const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state); |
| | 628 | const reset_event: *ResetEvent = @ptrCast(&group.context); |
| | 629 | GroupClosure.syncWaitUncancelable(group_state, reset_event); |
| 585 | | 630 | |
| 586 | var node: *std.SinglyLinkedList.Node = @ptrCast(@alignCast(token)); | 631 | var node: *std.SinglyLinkedList.Node = @ptrCast(@alignCast(token)); |
| 587 | while (true) { | 632 | while (true) { |
| ... | @@ -609,7 +654,7 @@ fn groupCancel(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void | ... | @@ -609,7 +654,7 @@ fn groupCancel(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void |
| 609 | | 654 | |
| 610 | const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state); | 655 | const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state); |
| 611 | const reset_event: *ResetEvent = @ptrCast(&group.context); | 656 | const reset_event: *ResetEvent = @ptrCast(&group.context); |
| 612 | std.Thread.WaitGroup.waitStateless(group_state, reset_event); | 657 | GroupClosure.syncWaitUncancelable(group_state, reset_event); |
| 613 | | 658 | |
| 614 | { | 659 | { |
| 615 | var node: *std.SinglyLinkedList.Node = @ptrCast(@alignCast(token)); | 660 | var node: *std.SinglyLinkedList.Node = @ptrCast(@alignCast(token)); |
| ... | @@ -661,22 +706,20 @@ fn checkCancel(t: *Threaded) error{Canceled}!void { | ... | @@ -661,22 +706,20 @@ fn checkCancel(t: *Threaded) error{Canceled}!void { |
| 661 | fn mutexLock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) Io.Cancelable!void { | 706 | fn mutexLock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) Io.Cancelable!void { |
| 662 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 707 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 663 | if (prev_state == .contended) { | 708 | if (prev_state == .contended) { |
| 664 | try t.checkCancel(); | 709 | try futexWait(t, @ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); |
| 665 | futexWait(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); | | |
| 666 | } | 710 | } |
| 667 | while (@atomicRmw(Io.Mutex.State, &mutex.state, .Xchg, .contended, .acquire) != .unlocked) { | 711 | while (@atomicRmw(Io.Mutex.State, &mutex.state, .Xchg, .contended, .acquire) != .unlocked) { |
| 668 | try t.checkCancel(); | 712 | try futexWait(t, @ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); |
| 669 | futexWait(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); | | |
| 670 | } | 713 | } |
| 671 | } | 714 | } |
| 672 | | 715 | |
| 673 | fn mutexLockUncancelable(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) void { | 716 | fn mutexLockUncancelable(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) void { |
| 674 | _ = userdata; | 717 | _ = userdata; |
| 675 | if (prev_state == .contended) { | 718 | if (prev_state == .contended) { |
| 676 | futexWait(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); | 719 | futexWaitUncancelable(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); |
| 677 | } | 720 | } |
| 678 | while (@atomicRmw(Io.Mutex.State, &mutex.state, .Xchg, .contended, .acquire) != .unlocked) { | 721 | while (@atomicRmw(Io.Mutex.State, &mutex.state, .Xchg, .contended, .acquire) != .unlocked) { |
| 679 | futexWait(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); | 722 | futexWaitUncancelable(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); |
| 680 | } | 723 | } |
| 681 | } | 724 | } |
| 682 | | 725 | |
| ... | @@ -708,7 +751,7 @@ fn conditionWaitUncancelable(userdata: ?*anyopaque, cond: *Io.Condition, mutex: | ... | @@ -708,7 +751,7 @@ fn conditionWaitUncancelable(userdata: ?*anyopaque, cond: *Io.Condition, mutex: |
| 708 | defer mutex.lockUncancelable(t_io); | 751 | defer mutex.lockUncancelable(t_io); |
| 709 | | 752 | |
| 710 | while (true) { | 753 | while (true) { |
| 711 | futexWait(cond_epoch, epoch); | 754 | futexWaitUncancelable(cond_epoch, epoch); |
| 712 | epoch = cond_epoch.load(.acquire); | 755 | epoch = cond_epoch.load(.acquire); |
| 713 | state = cond_state.load(.monotonic); | 756 | state = cond_state.load(.monotonic); |
| 714 | while (state & signal_mask != 0) { | 757 | while (state & signal_mask != 0) { |
| ... | @@ -747,8 +790,7 @@ fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) I | ... | @@ -747,8 +790,7 @@ fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) I |
| 747 | defer mutex.lockUncancelable(t.io()); | 790 | defer mutex.lockUncancelable(t.io()); |
| 748 | | 791 | |
| 749 | while (true) { | 792 | while (true) { |
| 750 | try t.checkCancel(); | 793 | try futexWait(t, cond_epoch, epoch); |
| 751 | futexWait(cond_epoch, epoch); | | |
| 752 | | 794 | |
| 753 | epoch = cond_epoch.load(.acquire); | 795 | epoch = cond_epoch.load(.acquire); |
| 754 | state = cond_state.load(.monotonic); | 796 | state = cond_state.load(.monotonic); |
| ... | @@ -1708,9 +1750,8 @@ fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { | ... | @@ -1708,9 +1750,8 @@ fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { |
| 1708 | } | 1750 | } |
| 1709 | } | 1751 | } |
| 1710 | | 1752 | |
| 1711 | fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize { | 1753 | fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) Io.Cancelable!usize { |
| 1712 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 1754 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 1713 | _ = t; | | |
| 1714 | | 1755 | |
| 1715 | var reset_event: ResetEvent = .unset; | 1756 | var reset_event: ResetEvent = .unset; |
| 1716 | | 1757 | |
| ... | @@ -1720,20 +1761,20 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize { | ... | @@ -1720,20 +1761,20 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize { |
| 1720 | for (futures[0..i]) |cleanup_future| { | 1761 | for (futures[0..i]) |cleanup_future| { |
| 1721 | const cleanup_closure: *AsyncClosure = @ptrCast(@alignCast(cleanup_future)); | 1762 | const cleanup_closure: *AsyncClosure = @ptrCast(@alignCast(cleanup_future)); |
| 1722 | if (@atomicRmw(?*ResetEvent, &cleanup_closure.select_condition, .Xchg, null, .seq_cst) == AsyncClosure.done_reset_event) { | 1763 | if (@atomicRmw(?*ResetEvent, &cleanup_closure.select_condition, .Xchg, null, .seq_cst) == AsyncClosure.done_reset_event) { |
| 1723 | cleanup_closure.reset_event.wait(); // Ensure no reference to our stack-allocated reset_event. | 1764 | cleanup_closure.reset_event.waitUncancelable(); // Ensure no reference to our stack-allocated reset_event. |
| 1724 | } | 1765 | } |
| 1725 | } | 1766 | } |
| 1726 | return i; | 1767 | return i; |
| 1727 | } | 1768 | } |
| 1728 | } | 1769 | } |
| 1729 | | 1770 | |
| 1730 | reset_event.wait(); | 1771 | try reset_event.wait(t); |
| 1731 | | 1772 | |
| 1732 | var result: ?usize = null; | 1773 | var result: ?usize = null; |
| 1733 | for (futures, 0..) |future, i| { | 1774 | for (futures, 0..) |future, i| { |
| 1734 | const closure: *AsyncClosure = @ptrCast(@alignCast(future)); | 1775 | const closure: *AsyncClosure = @ptrCast(@alignCast(future)); |
| 1735 | if (@atomicRmw(?*ResetEvent, &closure.select_condition, .Xchg, null, .seq_cst) == AsyncClosure.done_reset_event) { | 1776 | if (@atomicRmw(?*ResetEvent, &closure.select_condition, .Xchg, null, .seq_cst) == AsyncClosure.done_reset_event) { |
| 1736 | closure.reset_event.wait(); // Ensure no reference to our stack-allocated reset_event. | 1777 | closure.reset_event.waitUncancelable(); // Ensure no reference to our stack-allocated reset_event. |
| 1737 | if (result == null) result = i; // In case multiple are ready, return first. | 1778 | if (result == null) result = i; // In case multiple are ready, return first. |
| 1738 | } | 1779 | } |
| 1739 | } | 1780 | } |
| ... | @@ -3320,11 +3361,12 @@ fn copyCanon(canonical_name_buffer: *[HostName.max_len]u8, name: []const u8) Hos | ... | @@ -3320,11 +3361,12 @@ fn copyCanon(canonical_name_buffer: *[HostName.max_len]u8, name: []const u8) Hos |
| 3320 | return .{ .bytes = dest }; | 3361 | return .{ .bytes = dest }; |
| 3321 | } | 3362 | } |
| 3322 | | 3363 | |
| 3323 | pub fn futexWait(ptr: *const std.atomic.Value(u32), expect: u32) void { | 3364 | fn futexWait(t: *Threaded, ptr: *const std.atomic.Value(u32), expect: u32) Io.Cancelable!void { |
| 3324 | @branchHint(.cold); | 3365 | @branchHint(.cold); |
| 3325 | | 3366 | |
| 3326 | if (native_os == .linux) { | 3367 | if (native_os == .linux) { |
| 3327 | const linux = std.os.linux; | 3368 | const linux = std.os.linux; |
| | 3369 | try t.checkCancel(); |
| 3328 | const rc = linux.futex_4arg(ptr, .{ .cmd = .WAIT, .private = true }, expect, null); | 3370 | const rc = linux.futex_4arg(ptr, .{ .cmd = .WAIT, .private = true }, expect, null); |
| 3329 | if (builtin.mode == .Debug) switch (linux.E.init(rc)) { | 3371 | if (builtin.mode == .Debug) switch (linux.E.init(rc)) { |
| 3330 | .SUCCESS => {}, // notified by `wake()` | 3372 | .SUCCESS => {}, // notified by `wake()` |
| ... | @@ -3341,7 +3383,28 @@ pub fn futexWait(ptr: *const std.atomic.Value(u32), expect: u32) void { | ... | @@ -3341,7 +3383,28 @@ pub fn futexWait(ptr: *const std.atomic.Value(u32), expect: u32) void { |
| 3341 | @compileError("TODO"); | 3383 | @compileError("TODO"); |
| 3342 | } | 3384 | } |
| 3343 | | 3385 | |
| 3344 | pub fn futexWaitDuration(ptr: *const std.atomic.Value(u32), expect: u32, timeout: Io.Duration) void { | 3386 | pub fn futexWaitUncancelable(ptr: *const std.atomic.Value(u32), expect: u32) void { |
| | 3387 | @branchHint(.cold); |
| | 3388 | |
| | 3389 | if (native_os == .linux) { |
| | 3390 | const linux = std.os.linux; |
| | 3391 | const rc = linux.futex_4arg(ptr, .{ .cmd = .WAIT, .private = true }, expect, null); |
| | 3392 | if (builtin.mode == .Debug) switch (linux.E.init(rc)) { |
| | 3393 | .SUCCESS => {}, // notified by `wake()` |
| | 3394 | .INTR => {}, // gives caller a chance to check cancellation |
| | 3395 | .AGAIN => {}, // ptr.* != expect |
| | 3396 | .INVAL => {}, // possibly timeout overflow |
| | 3397 | .TIMEDOUT => unreachable, |
| | 3398 | .FAULT => unreachable, // ptr was invalid |
| | 3399 | else => unreachable, |
| | 3400 | }; |
| | 3401 | return; |
| | 3402 | } |
| | 3403 | |
| | 3404 | @compileError("TODO"); |
| | 3405 | } |
| | 3406 | |
| | 3407 | pub fn futexWaitDurationUncancelable(ptr: *const std.atomic.Value(u32), expect: u32, timeout: Io.Duration) void { |
| 3345 | @branchHint(.cold); | 3408 | @branchHint(.cold); |
| 3346 | | 3409 | |
| 3347 | if (native_os == .linux) { | 3410 | if (native_os == .linux) { |
| ... | @@ -3384,3 +3447,114 @@ pub fn futexWake(ptr: *const std.atomic.Value(u32), max_waiters: u32) void { | ... | @@ -3384,3 +3447,114 @@ pub fn futexWake(ptr: *const std.atomic.Value(u32), max_waiters: u32) void { |
| 3384 | | 3447 | |
| 3385 | @compileError("TODO"); | 3448 | @compileError("TODO"); |
| 3386 | } | 3449 | } |
| | 3450 | |
| | 3451 | /// A thread-safe logical boolean value which can be `set` and `unset`. |
| | 3452 | /// |
| | 3453 | /// It can also block threads until the value is set with cancelation via timed |
| | 3454 | /// waits. Statically initializable; four bytes on all targets. |
| | 3455 | pub const ResetEvent = enum(u32) { |
| | 3456 | unset = 0, |
| | 3457 | waiting = 1, |
| | 3458 | is_set = 2, |
| | 3459 | |
| | 3460 | /// Returns whether the logical boolean is `set`. |
| | 3461 | /// |
| | 3462 | /// Once `reset` is called, this returns false until the next `set`. |
| | 3463 | /// |
| | 3464 | /// The memory accesses before the `set` can be said to happen before |
| | 3465 | /// `isSet` returns true. |
| | 3466 | pub fn isSet(re: *const ResetEvent) bool { |
| | 3467 | if (builtin.single_threaded) return switch (re.*) { |
| | 3468 | .unset => false, |
| | 3469 | .waiting => unreachable, |
| | 3470 | .is_set => true, |
| | 3471 | }; |
| | 3472 | // Acquire barrier ensures memory accesses before `set` happen before |
| | 3473 | // returning true. |
| | 3474 | return @atomicLoad(ResetEvent, re, .acquire) == .is_set; |
| | 3475 | } |
| | 3476 | |
| | 3477 | /// Blocks the calling thread until `set` is called. |
| | 3478 | /// |
| | 3479 | /// This is effectively a more efficient version of `while (!isSet()) {}`. |
| | 3480 | /// |
| | 3481 | /// The memory accesses before the `set` can be said to happen before `wait` returns. |
| | 3482 | pub fn wait(re: *ResetEvent, t: *Threaded) Io.Cancelable!void { |
| | 3483 | if (builtin.single_threaded) switch (re.*) { |
| | 3484 | .unset => unreachable, // Deadlock, no other threads to wake us up. |
| | 3485 | .waiting => unreachable, // Invalid state. |
| | 3486 | .is_set => return, |
| | 3487 | }; |
| | 3488 | if (re.isSet()) { |
| | 3489 | @branchHint(.likely); |
| | 3490 | return; |
| | 3491 | } |
| | 3492 | // Try to set the state from `unset` to `waiting` to indicate to the |
| | 3493 | // `set` thread that others are blocked on the ResetEvent. Avoid using |
| | 3494 | // any strict barriers until we know the ResetEvent is set. |
| | 3495 | var state = @atomicLoad(ResetEvent, re, .acquire); |
| | 3496 | if (state == .unset) { |
| | 3497 | state = @cmpxchgStrong(ResetEvent, re, state, .waiting, .acquire, .acquire) orelse .waiting; |
| | 3498 | } |
| | 3499 | while (state == .waiting) { |
| | 3500 | try futexWait(t, @ptrCast(re), @intFromEnum(ResetEvent.waiting)); |
| | 3501 | state = @atomicLoad(ResetEvent, re, .acquire); |
| | 3502 | } |
| | 3503 | assert(state == .is_set); |
| | 3504 | } |
| | 3505 | |
| | 3506 | /// Same as `wait` except uninterruptible. |
| | 3507 | pub fn waitUncancelable(re: *ResetEvent) void { |
| | 3508 | if (builtin.single_threaded) switch (re.*) { |
| | 3509 | .unset => unreachable, // Deadlock, no other threads to wake us up. |
| | 3510 | .waiting => unreachable, // Invalid state. |
| | 3511 | .is_set => return, |
| | 3512 | }; |
| | 3513 | if (re.isSet()) { |
| | 3514 | @branchHint(.likely); |
| | 3515 | return; |
| | 3516 | } |
| | 3517 | // Try to set the state from `unset` to `waiting` to indicate to the |
| | 3518 | // `set` thread that others are blocked on the ResetEvent. Avoid using |
| | 3519 | // any strict barriers until we know the ResetEvent is set. |
| | 3520 | var state = @atomicLoad(ResetEvent, re, .acquire); |
| | 3521 | if (state == .unset) { |
| | 3522 | state = @cmpxchgStrong(ResetEvent, re, state, .waiting, .acquire, .acquire) orelse .waiting; |
| | 3523 | } |
| | 3524 | while (state == .waiting) { |
| | 3525 | futexWaitUncancelable(@ptrCast(re), @intFromEnum(ResetEvent.waiting)); |
| | 3526 | state = @atomicLoad(ResetEvent, re, .acquire); |
| | 3527 | } |
| | 3528 | assert(state == .is_set); |
| | 3529 | } |
| | 3530 | |
| | 3531 | /// Marks the logical boolean as `set` and unblocks any threads in `wait` |
| | 3532 | /// or `timedWait` to observe the new state. |
| | 3533 | /// |
| | 3534 | /// The logical boolean stays `set` until `reset` is called, making future |
| | 3535 | /// `set` calls do nothing semantically. |
| | 3536 | /// |
| | 3537 | /// The memory accesses before `set` can be said to happen before `isSet` |
| | 3538 | /// returns true or `wait`/`timedWait` return successfully. |
| | 3539 | pub fn set(re: *ResetEvent) void { |
| | 3540 | if (builtin.single_threaded) { |
| | 3541 | re.* = .is_set; |
| | 3542 | return; |
| | 3543 | } |
| | 3544 | if (@atomicRmw(ResetEvent, re, .Xchg, .is_set, .release) == .waiting) { |
| | 3545 | futexWake(@ptrCast(re), std.math.maxInt(u32)); |
| | 3546 | } |
| | 3547 | } |
| | 3548 | |
| | 3549 | /// Unmarks the ResetEvent as if `set` was never called. |
| | 3550 | /// |
| | 3551 | /// Assumes no threads are blocked in `wait` or `timedWait`. Concurrent |
| | 3552 | /// calls to `set`, `isSet` and `reset` are allowed. |
| | 3553 | pub fn reset(re: *ResetEvent) void { |
| | 3554 | if (builtin.single_threaded) { |
| | 3555 | re.* = .unset; |
| | 3556 | return; |
| | 3557 | } |
| | 3558 | @atomicStore(ResetEvent, re, .unset, .monotonic); |
| | 3559 | } |
| | 3560 | }; |