| ... | ... | @@ -1681,20 +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 | | try waitInner(cond, io, mutex, false); |
| 1690 | waitTimeout(cond, io, mutex, .none) catch |err| switch (err) { |
| 1691 | error.Timeout => unreachable, |
| 1692 | error.Canceled => |e| return e, |
| 1693 | }; |
| 1686 | 1694 | } |
| 1687 | 1695 | |
| 1688 | | /// Same as `wait`, except does not introduce a cancelation point. |
| 1696 | pub const WaitTimeoutError = Cancelable || Timeout.Error; |
| 1697 | |
| 1698 | /// Blocks until the condition is signaled, canceled, or the provided |
| 1699 | /// timeout expires. |
| 1689 | 1700 | /// |
| 1690 | | /// For a description of cancelation and cancelation points, see `Future.cancel`. |
| 1691 | | pub fn waitUncancelable(cond: *Condition, io: Io, mutex: *Mutex) void { |
| 1692 | | waitInner(cond, io, mutex, true) catch |err| switch (err) { |
| 1693 | | error.Canceled => unreachable, |
| 1694 | | }; |
| 1695 | | } |
| 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); |
| 1696 | 1706 | |
| 1697 | | fn waitInner(cond: *Condition, io: Io, mutex: *Mutex, uncancelable: bool) Cancelable!void { |
| 1698 | 1707 | var epoch = cond.epoch.load(.acquire); // `.acquire` to ensure ordered before state load |
| 1699 | 1708 | |
| 1700 | 1709 | { |
| ... | ... | @@ -1706,10 +1715,7 @@ pub const Condition = struct { |
| 1706 | 1715 | defer mutex.lockUncancelable(io); |
| 1707 | 1716 | |
| 1708 | 1717 | while (true) { |
| 1709 | | const result = if (uncancelable) |
| 1710 | | io.futexWaitUncancelable(u32, &cond.epoch.raw, epoch) |
| 1711 | | else |
| 1712 | | io.futexWait(u32, &cond.epoch.raw, epoch); |
| 1718 | const result = io.futexWaitTimeout(u32, &cond.epoch.raw, epoch, deadline); |
| 1713 | 1719 | |
| 1714 | 1720 | epoch = cond.epoch.load(.acquire); // `.acquire` to ensure ordered before `state` laod |
| 1715 | 1721 | |
| ... | ... | @@ -1729,13 +1735,62 @@ pub const Condition = struct { |
| 1729 | 1735 | } |
| 1730 | 1736 | |
| 1731 | 1737 | // There are no more signals available; this was a spurious wakeup or an error. If it |
| 1732 | | // was an error, we will remove ourselves as a waiter and return that error. Otherwise, |
| 1733 | | // we'll loop back to the futex wait. |
| 1738 | // was an error, we will remove ourselves as a waiter and return that error. If a |
| 1739 | // timeout was specified and the deadline has passed, we remove ourselves as a waiter |
| 1740 | // and return `error.Timeout`. Otherwise, we'll loop back to the futex wait. |
| 1734 | 1741 | result catch |err| { |
| 1735 | 1742 | const prev_state = cond.state.fetchSub(.{ .waiters = 1, .signals = 0 }, .monotonic); |
| 1736 | 1743 | assert(prev_state.waiters > 0); // underflow caused by illegal state |
| 1737 | 1744 | return err; |
| 1738 | 1745 | }; |
| 1746 | switch (deadline) { |
| 1747 | .none => {}, |
| 1748 | .deadline => |d| if (d.untilNow(io).raw.nanoseconds >= 0) { |
| 1749 | const prev_state = cond.state.fetchSub(.{ .waiters = 1, .signals = 0 }, .monotonic); |
| 1750 | assert(prev_state.waiters > 0); // underflow caused by illegal state |
| 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 | }; |
| 1789 | } |
| 1790 | } |
| 1791 | |
| 1792 | // There are no more signals available; this was a spurious wakeup, |
| 1793 | // so we'll loop back to the futex wait. |
| 1739 | 1794 | } |
| 1740 | 1795 | } |
| 1741 | 1796 | |