| ... | ... | @@ -1681,28 +1681,29 @@ pub const Condition = struct { |
| 1681 | 1681 | .epoch = .init(0), |
| 1682 | 1682 | }; |
| 1683 | 1683 | |
| 1684 | /// Blocks until the condition is signaled or canceled. |
| 1685 | /// |
| 1686 | /// See also: |
| 1687 | /// * `waitUncancelable` |
| 1688 | /// * `waitTimeout` |
| 1684 | 1689 | pub fn wait(cond: *Condition, io: Io, mutex: *Mutex) Cancelable!void { |
| 1685 | | waitInner(cond, io, mutex, .{ .timeout = .none }) catch |err| switch (err) { |
| 1690 | waitTimeout(cond, io, mutex, .none) catch |err| switch (err) { |
| 1686 | 1691 | error.Timeout => unreachable, |
| 1687 | | error.Canceled => return error.Canceled, |
| 1692 | error.Canceled => |e| return e, |
| 1688 | 1693 | }; |
| 1689 | 1694 | } |
| 1690 | 1695 | |
| 1691 | | pub fn waitTimeout(cond: *Condition, io: Io, mutex: *Mutex, timeout: Timeout) (Cancelable || Timeout.Error)!void { |
| 1692 | | return waitInner(cond, io, mutex, .{ .timeout = timeout.toDeadline(io) }); |
| 1693 | | } |
| 1696 | pub const WaitTimeoutError = Cancelable || Timeout.Error; |
| 1694 | 1697 | |
| 1695 | | /// Same as `wait`, except does not introduce a cancelation point. |
| 1698 | /// Blocks until the condition is signaled, canceled, or the provided |
| 1699 | /// timeout expires. |
| 1696 | 1700 | /// |
| 1697 | | /// For a description of cancelation and cancelation points, see `Future.cancel`. |
| 1698 | | pub fn waitUncancelable(cond: *Condition, io: Io, mutex: *Mutex) void { |
| 1699 | | waitInner(cond, io, mutex, .uncancelable) catch |err| switch (err) { |
| 1700 | | error.Timeout => unreachable, |
| 1701 | | error.Canceled => unreachable, |
| 1702 | | }; |
| 1703 | | } |
| 1701 | /// See also: |
| 1702 | /// * `wait` |
| 1703 | /// * `waitUncancelable` |
| 1704 | pub fn waitTimeout(cond: *Condition, io: Io, mutex: *Mutex, timeout: Timeout) WaitTimeoutError!void { |
| 1705 | const deadline = timeout.toDeadline(io); |
| 1704 | 1706 | |
| 1705 | | fn waitInner(cond: *Condition, io: Io, mutex: *Mutex, mode: union(enum) { uncancelable, timeout: Timeout }) (Cancelable || Timeout.Error)!void { |
| 1706 | 1707 | var epoch = cond.epoch.load(.acquire); // `.acquire` to ensure ordered before state load |
| 1707 | 1708 | |
| 1708 | 1709 | { |
| ... | ... | @@ -1714,10 +1715,7 @@ pub const Condition = struct { |
| 1714 | 1715 | defer mutex.lockUncancelable(io); |
| 1715 | 1716 | |
| 1716 | 1717 | while (true) { |
| 1717 | | const result = switch (mode) { |
| 1718 | | .uncancelable => io.futexWaitUncancelable(u32, &cond.epoch.raw, epoch), |
| 1719 | | .timeout => |t| io.futexWaitTimeout(u32, &cond.epoch.raw, epoch, t), |
| 1720 | | }; |
| 1718 | const result = io.futexWaitTimeout(u32, &cond.epoch.raw, epoch, deadline); |
| 1721 | 1719 | |
| 1722 | 1720 | epoch = cond.epoch.load(.acquire); // `.acquire` to ensure ordered before `state` laod |
| 1723 | 1721 | |
| ... | ... | @@ -1745,13 +1743,54 @@ pub const Condition = struct { |
| 1745 | 1743 | assert(prev_state.waiters > 0); // underflow caused by illegal state |
| 1746 | 1744 | return err; |
| 1747 | 1745 | }; |
| 1748 | | if (mode == .timeout and mode.timeout != .none) { |
| 1749 | | if (mode.timeout.deadline.untilNow(io).raw.nanoseconds >= 0) { |
| 1746 | switch (deadline) { |
| 1747 | .none => {}, |
| 1748 | .deadline => |d| if (d.untilNow(io).raw.nanoseconds >= 0) { |
| 1750 | 1749 | const prev_state = cond.state.fetchSub(.{ .waiters = 1, .signals = 0 }, .monotonic); |
| 1751 | 1750 | assert(prev_state.waiters > 0); // underflow caused by illegal state |
| 1752 | 1751 | return error.Timeout; |
| 1752 | }, |
| 1753 | .duration => unreachable, |
| 1754 | } |
| 1755 | } |
| 1756 | } |
| 1757 | |
| 1758 | /// Same as `wait`, except does not introduce a cancelation point. |
| 1759 | /// |
| 1760 | /// See `Future.cancel` for a description of cancelation points. |
| 1761 | pub fn waitUncancelable(cond: *Condition, io: Io, mutex: *Mutex) void { |
| 1762 | var epoch = cond.epoch.load(.acquire); // `.acquire` to ensure ordered before state load |
| 1763 | |
| 1764 | { |
| 1765 | const prev_state = cond.state.fetchAdd(.{ .waiters = 1, .signals = 0 }, .monotonic); |
| 1766 | assert(prev_state.waiters < math.maxInt(u16)); // overflow caused by too many waiters |
| 1767 | } |
| 1768 | |
| 1769 | mutex.unlock(io); |
| 1770 | defer mutex.lockUncancelable(io); |
| 1771 | |
| 1772 | while (true) { |
| 1773 | io.futexWaitUncancelable(u32, &cond.epoch.raw, epoch); |
| 1774 | |
| 1775 | epoch = cond.epoch.load(.acquire); // `.acquire` to ensure ordered before `state` laod |
| 1776 | |
| 1777 | // Even on error, try to consume a pending signal first. Otherwise a race might |
| 1778 | // cause a signal to get stuck in the state with no corresponding waiter. |
| 1779 | { |
| 1780 | var prev_state = cond.state.load(.monotonic); |
| 1781 | while (prev_state.signals > 0) { |
| 1782 | prev_state = cond.state.cmpxchgWeak(prev_state, .{ |
| 1783 | .waiters = prev_state.waiters - 1, |
| 1784 | .signals = prev_state.signals - 1, |
| 1785 | }, .acquire, .monotonic) orelse { |
| 1786 | // We successfully consumed a signal. |
| 1787 | return; |
| 1788 | }; |
| 1753 | 1789 | } |
| 1754 | 1790 | } |
| 1791 | |
| 1792 | // There are no more signals available; this was a spurious wakeup, |
| 1793 | // so we'll loop back to the futex wait. |
| 1755 | 1794 | } |
| 1756 | 1795 | } |
| 1757 | 1796 | |