| ... | ... | @@ -280,7 +280,7 @@ const Fiber = struct { |
| 280 | 280 | .select => if (@atomicRmw(i32, &fiber.await_count, .Add, 1, .monotonic) == -1) { |
| 281 | 281 | ev.queue.async(fiber, &Fiber.@"resume"); |
| 282 | 282 | }, |
| 283 | | _ => |awaiting| awaiting.toCancelable().canceled(), |
| 283 | _ => |awaiting| awaiting.toCancelable().async(), |
| 284 | 284 | } |
| 285 | 285 | } |
| 286 | 286 | |
| ... | ... | @@ -484,7 +484,7 @@ pub fn io(ev: *Evented) Io { |
| 484 | 484 | |
| 485 | 485 | pub const InitOptions = struct { |
| 486 | 486 | backing_allocator_needs_mutex: bool = true, |
| 487 | | queue: ?c.dispatch.queue_t = null, |
| 487 | target_queue: ?c.dispatch.queue_t = .TARGET_DEFAULT, |
| 488 | 488 | /// Upper limit on the allowable delay in processing timeouts in order to improve power |
| 489 | 489 | /// consumption and system performance. |
| 490 | 490 | leeway: Io.Duration = .fromMilliseconds(10), |
| ... | ... | @@ -499,11 +499,11 @@ pub const InitOptions = struct { |
| 499 | 499 | }; |
| 500 | 500 | |
| 501 | 501 | pub fn init(ev: *Evented, backing_allocator: Allocator, options: InitOptions) !void { |
| 502 | | const queue = if (options.queue) |queue| queue: { |
| 503 | | queue.as_object().retain(); |
| 504 | | break :queue queue; |
| 505 | | } else c.dispatch.queue_create("org.ziglang.std.Io.Dispatch", .CONCURRENT()) orelse |
| 506 | | return error.SystemResources; |
| 502 | const queue = c.dispatch.queue_create_with_target( |
| 503 | "org.ziglang.std.Io.Dispatch", |
| 504 | .CONCURRENT(), |
| 505 | options.target_queue, |
| 506 | ) orelse return error.SystemResources; |
| 507 | 507 | errdefer queue.as_object().release(); |
| 508 | 508 | const main_loop_stack = try backing_allocator.alignedAlloc( |
| 509 | 509 | u8, |
| ... | ... | @@ -727,9 +727,9 @@ const Cancelable = struct { |
| 727 | 727 | |
| 728 | 728 | const blocked: Cancelable = .{ .queue = undefined, .cancel = is_blocked }; |
| 729 | 729 | |
| 730 | | const AwaitError = error{CancelRequested}; |
| 730 | const RequestedError = error{CancelRequested}; |
| 731 | 731 | |
| 732 | | fn await(cancelable: *Cancelable, fiber: *Fiber) AwaitError!void { |
| 732 | fn enter(cancelable: *Cancelable, fiber: *Fiber) RequestedError!void { |
| 733 | 733 | const function = cancelable.cancel; |
| 734 | 734 | assert(function != is_requested); |
| 735 | 735 | if (function == is_blocked) { |
| ... | ... | @@ -750,13 +750,42 @@ const Cancelable = struct { |
| 750 | 750 | } |
| 751 | 751 | } |
| 752 | 752 | |
| 753 | | fn canceled(cancelable: *Cancelable) void { |
| 754 | | assert(cancelable.cancel != is_blocked); |
| 755 | | assert(cancelable.cancel != is_requested); |
| 756 | | cancelable.queue.async(cancelable, cancelable.cancel); |
| 753 | fn leave(cancelable: *Cancelable, fiber: *Fiber) RequestedError!void { |
| 754 | const function = cancelable.cancel; |
| 755 | assert(function != is_requested); |
| 756 | if (function == is_blocked) { |
| 757 | @branchHint(.unlikely); |
| 758 | return; |
| 759 | } |
| 760 | const cancel_status = @atomicRmw(Fiber.CancelStatus, &fiber.cancel_status, .And, .{ |
| 761 | .requested = true, |
| 762 | .awaiting = .nothing, |
| 763 | }, .monotonic); |
| 764 | assert(cancel_status.awaiting.toCancelable() == cancelable); |
| 765 | if (cancel_status.requested) return error.CancelRequested; |
| 766 | } |
| 767 | |
| 768 | fn async(cancelable: *Cancelable) void { |
| 769 | const function = cancelable.cancel; |
| 770 | assert(function != is_blocked and function != is_requested); |
| 771 | cancelable.queue.async(cancelable, function); |
| 772 | } |
| 773 | |
| 774 | fn requested(cancelable: *Cancelable, fiber: *Fiber) void { |
| 775 | const function = cancelable.cancel; |
| 776 | assert(function != is_blocked and function != is_requested); |
| 777 | assert(@atomicLoad(Fiber.CancelStatus, &fiber.cancel_status, .monotonic) == Fiber.CancelStatus{ |
| 778 | .requested = true, |
| 779 | .awaiting = .fromCancelable(cancelable), |
| 780 | }); |
| 781 | cancelable.cancel = is_requested; |
| 782 | @atomicStore(Fiber.CancelStatus, &fiber.cancel_status, .{ |
| 783 | .requested = true, |
| 784 | .awaiting = .nothing, |
| 785 | }, .monotonic); |
| 757 | 786 | } |
| 758 | 787 | |
| 759 | | fn check(cancelable: *Cancelable, fiber: *Fiber) Io.Cancelable!void { |
| 788 | fn acknowledge(cancelable: *Cancelable, fiber: *Fiber) Io.Cancelable!void { |
| 760 | 789 | if (cancelable.cancel == is_requested) { |
| 761 | 790 | @branchHint(.unlikely); |
| 762 | 791 | fiber.cancel_protection.acknowledge(); |
| ... | ... | @@ -784,81 +813,97 @@ const Sleeper = struct { |
| 784 | 813 | }; |
| 785 | 814 | |
| 786 | 815 | const Mutex = struct { |
| 787 | | /// including the locker |
| 788 | | num_waiters: usize, |
| 816 | state: State, |
| 789 | 817 | queue: c.dispatch.queue_t, |
| 790 | 818 | waiters: std.DoublyLinkedList, |
| 791 | 819 | |
| 820 | const State = packed struct(usize) { |
| 821 | locked: bool, |
| 822 | num_waiters: NumWaiters, |
| 823 | |
| 824 | const NumWaiters = @Int(.unsigned, @bitSizeOf(usize) - 1); |
| 825 | }; |
| 826 | |
| 792 | 827 | const Waiter = struct { |
| 793 | 828 | sleeper: Sleeper = undefined, |
| 794 | 829 | cancelable: Cancelable, |
| 795 | 830 | mutex: *Mutex, |
| 796 | | node: std.DoublyLinkedList.Node = .{}, |
| 831 | node: std.DoublyLinkedList.Node = undefined, |
| 797 | 832 | |
| 798 | 833 | fn add(context: ?*anyopaque) callconv(.c) void { |
| 799 | 834 | const waiter: *Waiter = @ptrCast(@alignCast(context)); |
| 800 | | waiter.tryAdd() catch |err| switch (err) { |
| 835 | waiter.cancelable.enter(waiter.sleeper.fiber) catch |err| switch (err) { |
| 836 | error.CancelRequested => return waiter.wake(), |
| 837 | }; |
| 838 | var state = @atomicRmw(State, &waiter.mutex.state, .Add, .{ |
| 839 | .locked = false, |
| 840 | .num_waiters = 1, |
| 841 | }, .monotonic); |
| 842 | state.num_waiters += 1; |
| 843 | while (!state.locked) { |
| 844 | @branchHint(.unlikely); |
| 845 | state = @cmpxchgWeak(State, &waiter.mutex.state, state, .{ |
| 846 | .locked = true, |
| 847 | .num_waiters = state.num_waiters - 1, |
| 848 | }, .acquire, .monotonic) orelse break; |
| 849 | } else return waiter.mutex.waiters.append(&waiter.node); |
| 850 | waiter.cancelable.leave(waiter.sleeper.fiber) catch |err| switch (err) { |
| 801 | 851 | error.CancelRequested => { |
| 802 | | waiter.wake(); |
| 803 | | assert(@atomicRmw(usize, &waiter.mutex.num_waiters, .Sub, 1, .monotonic) >= 1); |
| 852 | waiter.node.next = &waiter.node; |
| 853 | return; |
| 804 | 854 | }, |
| 805 | 855 | }; |
| 806 | | } |
| 807 | | |
| 808 | | fn tryAdd(waiter: *Waiter) Cancelable.AwaitError!void { |
| 809 | | switch (@atomicLoad(usize, &waiter.mutex.num_waiters, .acquire)) { |
| 810 | | 0 => unreachable, |
| 811 | | 1 => return waiter.wake(), // already locked exclusively |
| 812 | | else => try waiter.cancelable.await(waiter.sleeper.fiber), |
| 813 | | } |
| 814 | | waiter.mutex.waiters.append(&waiter.node); |
| 856 | waiter.wake(); |
| 815 | 857 | } |
| 816 | 858 | |
| 817 | 859 | fn canceled(context: ?*anyopaque) callconv(.c) void { |
| 818 | 860 | const cancelable: *Cancelable = @ptrCast(@alignCast(context)); |
| 819 | | cancelable.cancel = Cancelable.is_requested; |
| 820 | 861 | const waiter: *Waiter = @fieldParentPtr("cancelable", cancelable); |
| 821 | | assert(@atomicRmw( |
| 822 | | Fiber.CancelStatus, |
| 823 | | &waiter.sleeper.fiber.cancel_status, |
| 824 | | .Xchg, |
| 825 | | .{ .requested = true, .awaiting = .nothing }, |
| 826 | | .monotonic, |
| 827 | | ) == Fiber.CancelStatus{ .requested = true, .awaiting = .fromCancelable(cancelable) }); |
| 862 | cancelable.requested(waiter.sleeper.fiber); |
| 828 | 863 | const mutex = waiter.mutex; |
| 829 | | mutex.waiters.remove(&waiter.node); |
| 864 | if (waiter.node.next != &waiter.node) { |
| 865 | @branchHint(.likely); |
| 866 | mutex.waiters.remove(&waiter.node); |
| 867 | assert(@atomicRmw(State, &mutex.state, .Sub, .{ |
| 868 | .locked = false, |
| 869 | .num_waiters = 1, |
| 870 | }, .monotonic).num_waiters >= 1); |
| 871 | } |
| 872 | waiter.node = undefined; |
| 830 | 873 | waiter.wake(); |
| 831 | | assert(@atomicRmw(usize, &mutex.num_waiters, .Sub, 1, .monotonic) >= 1); |
| 832 | 874 | } |
| 833 | 875 | |
| 834 | 876 | fn remove(context: ?*anyopaque) callconv(.c) void { |
| 835 | 877 | const mutex: *Mutex = @ptrCast(@alignCast(context)); |
| 836 | | var stop_node: ?*std.DoublyLinkedList.Node = null; |
| 837 | | while (mutex.waiters.first != stop_node) { |
| 878 | var state = @atomicLoad(State, &mutex.state, .monotonic); |
| 879 | while (!state.locked and state.num_waiters > 0) { |
| 880 | @branchHint(.likely); |
| 881 | state = @cmpxchgWeak(State, &mutex.state, state, .{ |
| 882 | .locked = true, |
| 883 | .num_waiters = state.num_waiters - 1, |
| 884 | }, .acquire, .monotonic) orelse break; |
| 885 | } else return; |
| 886 | var num_removed: State.NumWaiters = 0; |
| 887 | while (mutex.waiters.popFirst()) |node| { |
| 838 | 888 | @branchHint(.likely); |
| 839 | | const waiter: *Waiter = @fieldParentPtr("node", mutex.waiters.popFirst().?); |
| 840 | | if (waiter.cancelable.cancel != Cancelable.is_blocked) { |
| 841 | | @branchHint(.likely); |
| 842 | | const cancel_status = @atomicRmw( |
| 843 | | Fiber.CancelStatus, |
| 844 | | &waiter.sleeper.fiber.cancel_status, |
| 845 | | .And, |
| 846 | | .{ .requested = true, .awaiting = .nothing }, |
| 847 | | .monotonic, |
| 848 | | ); |
| 849 | | assert(cancel_status.awaiting.toCancelable() == &waiter.cancelable); |
| 850 | | if (cancel_status.requested) { |
| 851 | | @branchHint(.unlikely); |
| 852 | | // carefully place the hot potato out of the way |
| 853 | | mutex.waiters.append(&waiter.node); |
| 854 | | if (stop_node == null) stop_node = &waiter.node; |
| 889 | const waiter: *Waiter = @fieldParentPtr("node", node); |
| 890 | node.* = undefined; |
| 891 | waiter.cancelable.leave(waiter.sleeper.fiber) catch |err| switch (err) { |
| 892 | error.CancelRequested => { |
| 893 | num_removed += 1; |
| 894 | node.next = node; |
| 855 | 895 | continue; |
| 856 | | } |
| 857 | | } |
| 858 | | waiter.wake(); |
| 859 | | return; |
| 896 | }, |
| 897 | }; |
| 898 | break; |
| 899 | } |
| 900 | if (num_removed > 0) { |
| 901 | @branchHint(.unlikely); |
| 902 | assert(@atomicRmw(State, &mutex.state, .Sub, .{ |
| 903 | .locked = false, |
| 904 | .num_waiters = num_removed, |
| 905 | }, .monotonic).num_waiters >= num_removed); |
| 860 | 906 | } |
| 861 | | // everyone is about to die, nobody will wake up ;-( |
| 862 | 907 | } |
| 863 | 908 | |
| 864 | 909 | fn wake(waiter: *Waiter) void { |
| ... | ... | @@ -868,7 +913,7 @@ const Mutex = struct { |
| 868 | 913 | |
| 869 | 914 | fn init(mutex: *Mutex, queue: c.dispatch.queue_t) error{SystemResources}!void { |
| 870 | 915 | mutex.* = .{ |
| 871 | | .num_waiters = 0, |
| 916 | .state = .{ .locked = false, .num_waiters = 0 }, |
| 872 | 917 | .queue = c.dispatch.queue_create_with_target( |
| 873 | 918 | "org.ziglang.std.Io.Dispatch.Mutex", |
| 874 | 919 | .SERIAL(), |
| ... | ... | @@ -879,56 +924,48 @@ const Mutex = struct { |
| 879 | 924 | } |
| 880 | 925 | |
| 881 | 926 | fn deinit(mutex: *Mutex) void { |
| 882 | | assert(mutex.num_waiters == 0 and mutex.waiters.first == null and mutex.waiters.last == null); |
| 927 | assert(mutex.state == State{ .locked = false, .num_waiters = 0 }); |
| 928 | assert(mutex.waiters.first == null and mutex.waiters.last == null); |
| 883 | 929 | mutex.queue.as_object().release(); |
| 884 | 930 | mutex.* = undefined; |
| 885 | 931 | } |
| 886 | 932 | |
| 887 | 933 | fn tryLock(mutex: *Mutex) bool { |
| 888 | | if (@cmpxchgWeak(usize, &mutex.num_waiters, 0, 1, .acquire, .monotonic) == null) { |
| 889 | | @branchHint(.likely); |
| 890 | | return true; |
| 934 | const state = |
| 935 | @atomicRmw(State, &mutex.state, .Or, .{ .locked = true, .num_waiters = 0 }, .acquire); |
| 936 | if (state.locked) { |
| 937 | @branchHint(.unlikely); |
| 891 | 938 | } |
| 892 | | return false; |
| 939 | return !state.locked; |
| 893 | 940 | } |
| 894 | 941 | |
| 895 | 942 | fn lock(mutex: *Mutex, ev: *Evented) Io.Cancelable!void { |
| 896 | | switch (@atomicRmw(usize, &mutex.num_waiters, .Add, 1, .acquire)) { |
| 897 | | 0 => {}, |
| 898 | | else => { |
| 899 | | @branchHint(.unlikely); |
| 900 | | var waiter: Waiter = .{ |
| 901 | | .cancelable = .{ .queue = mutex.queue, .cancel = &Mutex.Waiter.canceled }, |
| 902 | | .mutex = mutex, |
| 903 | | }; |
| 904 | | ev.yield(.{ .mutex_wait = &waiter }); |
| 905 | | try waiter.cancelable.check(waiter.sleeper.fiber); |
| 906 | | }, |
| 907 | | } |
| 943 | if (mutex.tryLock()) return; |
| 944 | var waiter: Waiter = .{ |
| 945 | .cancelable = .{ .queue = mutex.queue, .cancel = &Mutex.Waiter.canceled }, |
| 946 | .mutex = mutex, |
| 947 | }; |
| 948 | ev.yield(.{ .mutex_wait = &waiter }); |
| 949 | try waiter.cancelable.acknowledge(waiter.sleeper.fiber); |
| 908 | 950 | } |
| 909 | 951 | |
| 910 | 952 | fn lockUncancelable(mutex: *Mutex, ev: *Evented) void { |
| 911 | | switch (@atomicRmw(usize, &mutex.num_waiters, .Add, 1, .acquire)) { |
| 912 | | 0 => {}, |
| 913 | | else => { |
| 914 | | @branchHint(.unlikely); |
| 915 | | var waiter: Waiter = .{ .cancelable = .blocked, .mutex = mutex }; |
| 916 | | ev.yield(.{ .mutex_wait = &waiter }); |
| 917 | | waiter.cancelable.check(waiter.sleeper.fiber) catch |err| switch (err) { |
| 918 | | error.Canceled => unreachable, // blocked |
| 919 | | }; |
| 920 | | }, |
| 921 | | } |
| 953 | if (mutex.tryLock()) return; |
| 954 | var waiter: Waiter = .{ .cancelable = .blocked, .mutex = mutex }; |
| 955 | ev.yield(.{ .mutex_wait = &waiter }); |
| 956 | waiter.cancelable.acknowledge(waiter.sleeper.fiber) catch |err| switch (err) { |
| 957 | error.Canceled => unreachable, // blocked |
| 958 | }; |
| 922 | 959 | } |
| 923 | 960 | |
| 924 | 961 | fn unlock(mutex: *Mutex) void { |
| 925 | | switch (@atomicRmw(usize, &mutex.num_waiters, .Sub, 1, .release)) { |
| 926 | | 0 => unreachable, |
| 927 | | 1 => {}, |
| 928 | | else => { |
| 929 | | @branchHint(.unlikely); |
| 930 | | mutex.queue.async(mutex, &Waiter.remove); |
| 931 | | }, |
| 962 | const state = @atomicRmw(State, &mutex.state, .And, .{ |
| 963 | .locked = false, |
| 964 | .num_waiters = std.math.maxInt(State.NumWaiters), |
| 965 | }, .release); |
| 966 | if (state.num_waiters > 0) { |
| 967 | @branchHint(.unlikely); |
| 968 | mutex.queue.async(mutex, &Waiter.remove); |
| 932 | 969 | } |
| 933 | 970 | } |
| 934 | 971 | }; |
| ... | ... | @@ -1517,10 +1554,10 @@ const Futex = struct { |
| 1517 | 1554 | }; |
| 1518 | 1555 | } |
| 1519 | 1556 | |
| 1520 | | fn tryAdd(waiter: *Waiter) Cancelable.AwaitError!void { |
| 1557 | fn tryAdd(waiter: *Waiter) Cancelable.RequestedError!void { |
| 1521 | 1558 | if (@atomicLoad(u32, waiter.ptr, .monotonic) != waiter.expected) |
| 1522 | 1559 | return error.CancelRequested; |
| 1523 | | try waiter.cancelable.await(waiter.sleeper.fiber); |
| 1560 | try waiter.cancelable.enter(waiter.sleeper.fiber); |
| 1524 | 1561 | const futex = waiter.futex; |
| 1525 | 1562 | switch (waiter.timeout) { |
| 1526 | 1563 | .FOREVER => {}, |
| ... | ... | @@ -1542,46 +1579,28 @@ const Futex = struct { |
| 1542 | 1579 | |
| 1543 | 1580 | fn canceled(context: ?*anyopaque) callconv(.c) void { |
| 1544 | 1581 | const cancelable: *Cancelable = @ptrCast(@alignCast(context)); |
| 1545 | | cancelable.cancel = Cancelable.is_requested; |
| 1546 | 1582 | const waiter: *Waiter = @fieldParentPtr("cancelable", cancelable); |
| 1547 | | assert(@atomicRmw( |
| 1548 | | Fiber.CancelStatus, |
| 1549 | | &waiter.sleeper.fiber.cancel_status, |
| 1550 | | .Xchg, |
| 1551 | | .{ .requested = true, .awaiting = .nothing }, |
| 1552 | | .monotonic, |
| 1553 | | ) == Fiber.CancelStatus{ .requested = true, .awaiting = .fromCancelable(cancelable) }); |
| 1583 | cancelable.requested(waiter.sleeper.fiber); |
| 1554 | 1584 | const futex = waiter.futex; |
| 1555 | | waiter.removeUncancelable(); |
| 1585 | waiter.remove(); |
| 1556 | 1586 | assert(@atomicRmw(usize, &futex.num_waiters, .Sub, 1, .monotonic) >= 1); |
| 1557 | 1587 | } |
| 1558 | 1588 | |
| 1559 | 1589 | fn timedOut(context: ?*anyopaque) callconv(.c) void { |
| 1560 | 1590 | const waiter: *Waiter = @ptrCast(@alignCast(context)); |
| 1561 | 1591 | const futex = waiter.futex; |
| 1562 | | waiter.remove() catch |err| switch (err) { |
| 1592 | waiter.tryRemove() catch |err| switch (err) { |
| 1563 | 1593 | error.CancelRequested => return, |
| 1564 | 1594 | }; |
| 1565 | 1595 | assert(@atomicRmw(usize, &futex.num_waiters, .Sub, 1, .monotonic) >= 1); |
| 1566 | 1596 | } |
| 1567 | 1597 | |
| 1568 | | fn remove(waiter: *Waiter) Cancelable.AwaitError!void { |
| 1569 | | if (waiter.cancelable.cancel != Cancelable.is_blocked) { |
| 1570 | | @branchHint(.likely); |
| 1571 | | const cancel_status = @atomicRmw( |
| 1572 | | Fiber.CancelStatus, |
| 1573 | | &waiter.sleeper.fiber.cancel_status, |
| 1574 | | .And, |
| 1575 | | .{ .requested = true, .awaiting = .nothing }, |
| 1576 | | .monotonic, |
| 1577 | | ); |
| 1578 | | assert(cancel_status.awaiting.toCancelable() == &waiter.cancelable); |
| 1579 | | if (cancel_status.requested) return error.CancelRequested; |
| 1580 | | } |
| 1581 | | waiter.removeUncancelable(); |
| 1598 | fn tryRemove(waiter: *Waiter) Cancelable.RequestedError!void { |
| 1599 | try waiter.cancelable.leave(waiter.sleeper.fiber); |
| 1600 | waiter.remove(); |
| 1582 | 1601 | } |
| 1583 | 1602 | |
| 1584 | | fn removeUncancelable(waiter: *Waiter) void { |
| 1603 | fn remove(waiter: *Waiter) void { |
| 1585 | 1604 | waiter.futex.waiters.remove(&waiter.node); |
| 1586 | 1605 | if (waiter.timer) |timer| timer.cancel() else wake(waiter); |
| 1587 | 1606 | } |
| ... | ... | @@ -1614,7 +1633,7 @@ const Futex = struct { |
| 1614 | 1633 | @branchHint(.unlikely); |
| 1615 | 1634 | continue; |
| 1616 | 1635 | } |
| 1617 | | waiter.remove() catch |err| switch (err) { |
| 1636 | waiter.tryRemove() catch |err| switch (err) { |
| 1618 | 1637 | error.CancelRequested => continue, |
| 1619 | 1638 | }; |
| 1620 | 1639 | num_removed += 1; |
| ... | ... | @@ -1720,7 +1739,7 @@ fn futexWait( |
| 1720 | 1739 | .leeway = ev.leeway, |
| 1721 | 1740 | }; |
| 1722 | 1741 | ev.yield(.{ .futex_wait = &waiter }); |
| 1723 | | try waiter.cancelable.check(waiter.sleeper.fiber); |
| 1742 | try waiter.cancelable.acknowledge(waiter.sleeper.fiber); |
| 1724 | 1743 | } |
| 1725 | 1744 | |
| 1726 | 1745 | fn futexWaitUncancelable(userdata: ?*anyopaque, ptr: *const u32, expected: u32) void { |
| ... | ... | @@ -1734,7 +1753,7 @@ fn futexWaitUncancelable(userdata: ?*anyopaque, ptr: *const u32, expected: u32) |
| 1734 | 1753 | .leeway = ev.leeway, |
| 1735 | 1754 | }; |
| 1736 | 1755 | ev.yield(.{ .futex_wait = &waiter }); |
| 1737 | | waiter.cancelable.check(waiter.sleeper.fiber) catch |err| switch (err) { |
| 1756 | waiter.cancelable.acknowledge(waiter.sleeper.fiber) catch |err| switch (err) { |
| 1738 | 1757 | error.Canceled => unreachable, // blocked |
| 1739 | 1758 | }; |
| 1740 | 1759 | } |