| ... | @@ -19,129 +19,11 @@ pub const Mutex = @import("Thread/Mutex.zig"); | ... | @@ -19,129 +19,11 @@ pub const Mutex = @import("Thread/Mutex.zig"); |
| 19 | pub const Semaphore = @import("Thread/Semaphore.zig"); | 19 | pub const Semaphore = @import("Thread/Semaphore.zig"); |
| 20 | pub const Condition = @import("Thread/Condition.zig"); | 20 | pub const Condition = @import("Thread/Condition.zig"); |
| 21 | pub const RwLock = @import("Thread/RwLock.zig"); | 21 | pub const RwLock = @import("Thread/RwLock.zig"); |
| 22 | pub const WaitGroup = @import("Thread/WaitGroup.zig"); | | |
| 23 | | 22 | |
| 24 | pub const Pool = @compileError("deprecated; consider using 'std.Io.Group' with 'std.Io.Threaded'"); | 23 | pub const Pool = @compileError("deprecated; consider using 'std.Io.Group' with 'std.Io.Threaded'"); |
| 25 | | 24 | |
| 26 | pub const use_pthreads = native_os != .windows and native_os != .wasi and builtin.link_libc; | 25 | pub const use_pthreads = native_os != .windows and native_os != .wasi and builtin.link_libc; |
| 27 | | 26 | |
| 28 | /// A thread-safe logical boolean value which can be `set` and `unset`. | | |
| 29 | /// | | |
| 30 | /// It can also block threads until the value is set with cancelation via timed | | |
| 31 | /// waits. Statically initializable; four bytes on all targets. | | |
| 32 | pub const ResetEvent = enum(u32) { | | |
| 33 | unset = 0, | | |
| 34 | waiting = 1, | | |
| 35 | is_set = 2, | | |
| 36 | | | |
| 37 | /// Returns whether the logical boolean is `set`. | | |
| 38 | /// | | |
| 39 | /// Once `reset` is called, this returns false until the next `set`. | | |
| 40 | /// | | |
| 41 | /// The memory accesses before the `set` can be said to happen before | | |
| 42 | /// `isSet` returns true. | | |
| 43 | pub fn isSet(re: *const ResetEvent) bool { | | |
| 44 | if (builtin.single_threaded) return switch (re.*) { | | |
| 45 | .unset => false, | | |
| 46 | .waiting => unreachable, | | |
| 47 | .is_set => true, | | |
| 48 | }; | | |
| 49 | // Acquire barrier ensures memory accesses before `set` happen before | | |
| 50 | // returning true. | | |
| 51 | return @atomicLoad(ResetEvent, re, .acquire) == .is_set; | | |
| 52 | } | | |
| 53 | | | |
| 54 | /// Blocks the calling thread until `set` is called. | | |
| 55 | /// | | |
| 56 | /// This is effectively a more efficient version of `while (!isSet()) {}`. | | |
| 57 | /// | | |
| 58 | /// The memory accesses before the `set` can be said to happen before `wait` returns. | | |
| 59 | pub fn wait(re: *ResetEvent) void { | | |
| 60 | if (builtin.single_threaded) switch (re.*) { | | |
| 61 | .unset => unreachable, // Deadlock, no other threads to wake us up. | | |
| 62 | .waiting => unreachable, // Invalid state. | | |
| 63 | .is_set => return, | | |
| 64 | }; | | |
| 65 | if (!re.isSet()) return timedWaitInner(re, null) catch |err| switch (err) { | | |
| 66 | error.Timeout => unreachable, // No timeout specified. | | |
| 67 | }; | | |
| 68 | } | | |
| 69 | | | |
| 70 | /// Blocks the calling thread until `set` is called, or until the | | |
| 71 | /// corresponding timeout expires, returning `error.Timeout`. | | |
| 72 | /// | | |
| 73 | /// This is effectively a more efficient version of `while (!isSet()) {}`. | | |
| 74 | /// | | |
| 75 | /// The memory accesses before the set() can be said to happen before | | |
| 76 | /// timedWait() returns without error. | | |
| 77 | pub fn timedWait(re: *ResetEvent, timeout_ns: u64) error{Timeout}!void { | | |
| 78 | if (builtin.single_threaded) switch (re.*) { | | |
| 79 | .unset => return error.Timeout, | | |
| 80 | .waiting => unreachable, // Invalid state. | | |
| 81 | .is_set => return, | | |
| 82 | }; | | |
| 83 | if (!re.isSet()) return timedWaitInner(re, timeout_ns); | | |
| 84 | } | | |
| 85 | | | |
| 86 | fn timedWaitInner(re: *ResetEvent, timeout: ?u64) error{Timeout}!void { | | |
| 87 | @branchHint(.cold); | | |
| 88 | | | |
| 89 | // Try to set the state from `unset` to `waiting` to indicate to the | | |
| 90 | // `set` thread that others are blocked on the ResetEvent. Avoid using | | |
| 91 | // any strict barriers until we know the ResetEvent is set. | | |
| 92 | var state = @atomicLoad(ResetEvent, re, .acquire); | | |
| 93 | if (state == .unset) { | | |
| 94 | state = @cmpxchgStrong(ResetEvent, re, state, .waiting, .acquire, .acquire) orelse .waiting; | | |
| 95 | } | | |
| 96 | | | |
| 97 | // Wait until the ResetEvent is set since the state is waiting. | | |
| 98 | if (state == .waiting) { | | |
| 99 | var futex_deadline = Futex.Deadline.init(timeout); | | |
| 100 | while (true) { | | |
| 101 | const wait_result = futex_deadline.wait(@ptrCast(re), @intFromEnum(ResetEvent.waiting)); | | |
| 102 | | | |
| 103 | // Check if the ResetEvent was set before possibly reporting error.Timeout below. | | |
| 104 | state = @atomicLoad(ResetEvent, re, .acquire); | | |
| 105 | if (state != .waiting) break; | | |
| 106 | | | |
| 107 | try wait_result; | | |
| 108 | } | | |
| 109 | } | | |
| 110 | | | |
| 111 | assert(state == .is_set); | | |
| 112 | } | | |
| 113 | | | |
| 114 | /// Marks the logical boolean as `set` and unblocks any threads in `wait` | | |
| 115 | /// or `timedWait` to observe the new state. | | |
| 116 | /// | | |
| 117 | /// The logical boolean stays `set` until `reset` is called, making future | | |
| 118 | /// `set` calls do nothing semantically. | | |
| 119 | /// | | |
| 120 | /// The memory accesses before `set` can be said to happen before `isSet` | | |
| 121 | /// returns true or `wait`/`timedWait` return successfully. | | |
| 122 | pub fn set(re: *ResetEvent) void { | | |
| 123 | if (builtin.single_threaded) { | | |
| 124 | re.* = .is_set; | | |
| 125 | return; | | |
| 126 | } | | |
| 127 | if (@atomicRmw(ResetEvent, re, .Xchg, .is_set, .release) == .waiting) { | | |
| 128 | Futex.wake(@ptrCast(re), std.math.maxInt(u32)); | | |
| 129 | } | | |
| 130 | } | | |
| 131 | | | |
| 132 | /// Unmarks the ResetEvent as if `set` was never called. | | |
| 133 | /// | | |
| 134 | /// Assumes no threads are blocked in `wait` or `timedWait`. Concurrent | | |
| 135 | /// calls to `set`, `isSet` and `reset` are allowed. | | |
| 136 | pub fn reset(re: *ResetEvent) void { | | |
| 137 | if (builtin.single_threaded) { | | |
| 138 | re.* = .unset; | | |
| 139 | return; | | |
| 140 | } | | |
| 141 | @atomicStore(ResetEvent, re, .unset, .monotonic); | | |
| 142 | } | | |
| 143 | }; | | |
| 144 | | | |
| 145 | const Thread = @This(); | 27 | const Thread = @This(); |
| 146 | const Impl = if (native_os == .windows) | 28 | const Impl = if (native_os == .windows) |
| 147 | WindowsThreadImpl | 29 | WindowsThreadImpl |
| ... | @@ -1676,16 +1558,16 @@ test "setName, getName" { | ... | @@ -1676,16 +1558,16 @@ test "setName, getName" { |
| 1676 | const io = testing.io; | 1558 | const io = testing.io; |
| 1677 | | 1559 | |
| 1678 | const Context = struct { | 1560 | const Context = struct { |
| 1679 | start_wait_event: ResetEvent = .unset, | 1561 | start_wait_event: Io.Event = .unset, |
| 1680 | test_done_event: ResetEvent = .unset, | 1562 | test_done_event: Io.Event = .unset, |
| 1681 | thread_done_event: ResetEvent = .unset, | 1563 | thread_done_event: Io.Event = .unset, |
| 1682 | | 1564 | |
| 1683 | done: std.atomic.Value(bool) = std.atomic.Value(bool).init(false), | 1565 | done: std.atomic.Value(bool) = std.atomic.Value(bool).init(false), |
| 1684 | thread: Thread = undefined, | 1566 | thread: Thread = undefined, |
| 1685 | | 1567 | |
| 1686 | pub fn run(ctx: *@This()) !void { | 1568 | pub fn run(ctx: *@This()) !void { |
| 1687 | // Wait for the main thread to have set the thread field in the context. | 1569 | // Wait for the main thread to have set the thread field in the context. |
| 1688 | ctx.start_wait_event.wait(); | 1570 | try ctx.start_wait_event.wait(io); |
| 1689 | | 1571 | |
| 1690 | switch (native_os) { | 1572 | switch (native_os) { |
| 1691 | .windows => testThreadName(io, &ctx.thread) catch |err| switch (err) { | 1573 | .windows => testThreadName(io, &ctx.thread) catch |err| switch (err) { |
| ... | @@ -1696,10 +1578,10 @@ test "setName, getName" { | ... | @@ -1696,10 +1578,10 @@ test "setName, getName" { |
| 1696 | } | 1578 | } |
| 1697 | | 1579 | |
| 1698 | // Signal our test is done | 1580 | // Signal our test is done |
| 1699 | ctx.test_done_event.set(); | 1581 | ctx.test_done_event.set(io); |
| 1700 | | 1582 | |
| 1701 | // wait for the thread to property exit | 1583 | // wait for the thread to property exit |
| 1702 | ctx.thread_done_event.wait(); | 1584 | try ctx.thread_done_event.wait(io); |
| 1703 | } | 1585 | } |
| 1704 | }; | 1586 | }; |
| 1705 | | 1587 | |
| ... | @@ -1707,8 +1589,8 @@ test "setName, getName" { | ... | @@ -1707,8 +1589,8 @@ test "setName, getName" { |
| 1707 | var thread = try spawn(.{}, Context.run, .{&context}); | 1589 | var thread = try spawn(.{}, Context.run, .{&context}); |
| 1708 | | 1590 | |
| 1709 | context.thread = thread; | 1591 | context.thread = thread; |
| 1710 | context.start_wait_event.set(); | 1592 | context.start_wait_event.set(io); |
| 1711 | context.test_done_event.wait(); | 1593 | try context.test_done_event.wait(io); |
| 1712 | | 1594 | |
| 1713 | switch (native_os) { | 1595 | switch (native_os) { |
| 1714 | .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { | 1596 | .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { |
| ... | @@ -1722,31 +1604,32 @@ test "setName, getName" { | ... | @@ -1722,31 +1604,32 @@ test "setName, getName" { |
| 1722 | else => try testThreadName(io, &thread), | 1604 | else => try testThreadName(io, &thread), |
| 1723 | } | 1605 | } |
| 1724 | | 1606 | |
| 1725 | context.thread_done_event.set(); | 1607 | context.thread_done_event.set(io); |
| 1726 | thread.join(); | 1608 | thread.join(); |
| 1727 | } | 1609 | } |
| 1728 | | 1610 | |
| 1729 | test { | 1611 | test { |
| 1730 | _ = Futex; | 1612 | _ = Futex; |
| 1731 | _ = ResetEvent; | | |
| 1732 | _ = Mutex; | 1613 | _ = Mutex; |
| 1733 | _ = Semaphore; | 1614 | _ = Semaphore; |
| 1734 | _ = Condition; | 1615 | _ = Condition; |
| 1735 | _ = RwLock; | 1616 | _ = RwLock; |
| 1736 | } | 1617 | } |
| 1737 | | 1618 | |
| 1738 | fn testIncrementNotify(value: *usize, event: *ResetEvent) void { | 1619 | fn testIncrementNotify(io: Io, value: *usize, event: *Io.Event) void { |
| 1739 | value.* += 1; | 1620 | value.* += 1; |
| 1740 | event.set(); | 1621 | event.set(io); |
| 1741 | } | 1622 | } |
| 1742 | | 1623 | |
| 1743 | test join { | 1624 | test join { |
| 1744 | if (builtin.single_threaded) return error.SkipZigTest; | 1625 | if (builtin.single_threaded) return error.SkipZigTest; |
| 1745 | | 1626 | |
| | 1627 | const io = testing.io; |
| | 1628 | |
| 1746 | var value: usize = 0; | 1629 | var value: usize = 0; |
| 1747 | var event: ResetEvent = .unset; | 1630 | var event: Io.Event = .unset; |
| 1748 | | 1631 | |
| 1749 | const thread = try Thread.spawn(.{}, testIncrementNotify, .{ &value, &event }); | 1632 | const thread = try Thread.spawn(.{}, testIncrementNotify, .{ io, &value, &event }); |
| 1750 | thread.join(); | 1633 | thread.join(); |
| 1751 | | 1634 | |
| 1752 | try std.testing.expectEqual(value, 1); | 1635 | try std.testing.expectEqual(value, 1); |
| ... | @@ -1755,13 +1638,15 @@ test join { | ... | @@ -1755,13 +1638,15 @@ test join { |
| 1755 | test detach { | 1638 | test detach { |
| 1756 | if (builtin.single_threaded) return error.SkipZigTest; | 1639 | if (builtin.single_threaded) return error.SkipZigTest; |
| 1757 | | 1640 | |
| | 1641 | const io = testing.io; |
| | 1642 | |
| 1758 | var value: usize = 0; | 1643 | var value: usize = 0; |
| 1759 | var event: ResetEvent = .unset; | 1644 | var event: Io.Event = .unset; |
| 1760 | | 1645 | |
| 1761 | const thread = try Thread.spawn(.{}, testIncrementNotify, .{ &value, &event }); | 1646 | const thread = try Thread.spawn(.{}, testIncrementNotify, .{ io, &value, &event }); |
| 1762 | thread.detach(); | 1647 | thread.detach(); |
| 1763 | | 1648 | |
| 1764 | event.wait(); | 1649 | try event.wait(io); |
| 1765 | try std.testing.expectEqual(value, 1); | 1650 | try std.testing.expectEqual(value, 1); |
| 1766 | } | 1651 | } |
| 1767 | | 1652 | |
| ... | @@ -1803,127 +1688,6 @@ fn testTls() !void { | ... | @@ -1803,127 +1688,6 @@ fn testTls() !void { |
| 1803 | if (x != 1235) return error.TlsBadEndValue; | 1688 | if (x != 1235) return error.TlsBadEndValue; |
| 1804 | } | 1689 | } |
| 1805 | | 1690 | |
| 1806 | test "ResetEvent smoke test" { | | |
| 1807 | var event: ResetEvent = .unset; | | |
| 1808 | try testing.expectEqual(false, event.isSet()); | | |
| 1809 | | | |
| 1810 | // make sure the event gets set | | |
| 1811 | event.set(); | | |
| 1812 | try testing.expectEqual(true, event.isSet()); | | |
| 1813 | | | |
| 1814 | // make sure the event gets unset again | | |
| 1815 | event.reset(); | | |
| 1816 | try testing.expectEqual(false, event.isSet()); | | |
| 1817 | | | |
| 1818 | // waits should timeout as there's no other thread to set the event | | |
| 1819 | try testing.expectError(error.Timeout, event.timedWait(0)); | | |
| 1820 | try testing.expectError(error.Timeout, event.timedWait(std.time.ns_per_ms)); | | |
| 1821 | | | |
| 1822 | // set the event again and make sure waits complete | | |
| 1823 | event.set(); | | |
| 1824 | event.wait(); | | |
| 1825 | try event.timedWait(std.time.ns_per_ms); | | |
| 1826 | try testing.expectEqual(true, event.isSet()); | | |
| 1827 | } | | |
| 1828 | | | |
| 1829 | test "ResetEvent signaling" { | | |
| 1830 | // This test requires spawning threads | | |
| 1831 | if (builtin.single_threaded) { | | |
| 1832 | return error.SkipZigTest; | | |
| 1833 | } | | |
| 1834 | | | |
| 1835 | const Context = struct { | | |
| 1836 | in: ResetEvent = .unset, | | |
| 1837 | out: ResetEvent = .unset, | | |
| 1838 | value: usize = 0, | | |
| 1839 | | | |
| 1840 | fn input(self: *@This()) !void { | | |
| 1841 | // wait for the value to become 1 | | |
| 1842 | self.in.wait(); | | |
| 1843 | self.in.reset(); | | |
| 1844 | try testing.expectEqual(self.value, 1); | | |
| 1845 | | | |
| 1846 | // bump the value and wake up output() | | |
| 1847 | self.value = 2; | | |
| 1848 | self.out.set(); | | |
| 1849 | | | |
| 1850 | // wait for output to receive 2, bump the value and wake us up with 3 | | |
| 1851 | self.in.wait(); | | |
| 1852 | self.in.reset(); | | |
| 1853 | try testing.expectEqual(self.value, 3); | | |
| 1854 | | | |
| 1855 | // bump the value and wake up output() for it to see 4 | | |
| 1856 | self.value = 4; | | |
| 1857 | self.out.set(); | | |
| 1858 | } | | |
| 1859 | | | |
| 1860 | fn output(self: *@This()) !void { | | |
| 1861 | // start with 0 and bump the value for input to see 1 | | |
| 1862 | try testing.expectEqual(self.value, 0); | | |
| 1863 | self.value = 1; | | |
| 1864 | self.in.set(); | | |
| 1865 | | | |
| 1866 | // wait for input to receive 1, bump the value to 2 and wake us up | | |
| 1867 | self.out.wait(); | | |
| 1868 | self.out.reset(); | | |
| 1869 | try testing.expectEqual(self.value, 2); | | |
| 1870 | | | |
| 1871 | // bump the value to 3 for input to see (rhymes) | | |
| 1872 | self.value = 3; | | |
| 1873 | self.in.set(); | | |
| 1874 | | | |
| 1875 | // wait for input to bump the value to 4 and receive no more (rhymes) | | |
| 1876 | self.out.wait(); | | |
| 1877 | self.out.reset(); | | |
| 1878 | try testing.expectEqual(self.value, 4); | | |
| 1879 | } | | |
| 1880 | }; | | |
| 1881 | | | |
| 1882 | var ctx = Context{}; | | |
| 1883 | | | |
| 1884 | const thread = try std.Thread.spawn(.{}, Context.output, .{&ctx}); | | |
| 1885 | defer thread.join(); | | |
| 1886 | | | |
| 1887 | try ctx.input(); | | |
| 1888 | } | | |
| 1889 | | | |
| 1890 | test "ResetEvent broadcast" { | | |
| 1891 | // This test requires spawning threads | | |
| 1892 | if (builtin.single_threaded) { | | |
| 1893 | return error.SkipZigTest; | | |
| 1894 | } | | |
| 1895 | | | |
| 1896 | const num_threads = 10; | | |
| 1897 | const Barrier = struct { | | |
| 1898 | event: ResetEvent = .unset, | | |
| 1899 | counter: std.atomic.Value(usize) = std.atomic.Value(usize).init(num_threads), | | |
| 1900 | | | |
| 1901 | fn wait(self: *@This()) void { | | |
| 1902 | if (self.counter.fetchSub(1, .acq_rel) == 1) { | | |
| 1903 | self.event.set(); | | |
| 1904 | } | | |
| 1905 | } | | |
| 1906 | }; | | |
| 1907 | | | |
| 1908 | const Context = struct { | | |
| 1909 | start_barrier: Barrier = .{}, | | |
| 1910 | finish_barrier: Barrier = .{}, | | |
| 1911 | | | |
| 1912 | fn run(self: *@This()) void { | | |
| 1913 | self.start_barrier.wait(); | | |
| 1914 | self.finish_barrier.wait(); | | |
| 1915 | } | | |
| 1916 | }; | | |
| 1917 | | | |
| 1918 | var ctx = Context{}; | | |
| 1919 | var threads: [num_threads - 1]std.Thread = undefined; | | |
| 1920 | | | |
| 1921 | for (&threads) |*t| t.* = try std.Thread.spawn(.{}, Context.run, .{&ctx}); | | |
| 1922 | defer for (threads) |t| t.join(); | | |
| 1923 | | | |
| 1924 | ctx.run(); | | |
| 1925 | } | | |
| 1926 | | | |
| 1927 | /// Configures the per-thread alternative signal stack requested by `std.options.signal_stack_size`. | 1691 | /// Configures the per-thread alternative signal stack requested by `std.options.signal_stack_size`. |
| 1928 | pub fn maybeAttachSignalStack() void { | 1692 | pub fn maybeAttachSignalStack() void { |
| 1929 | const size = std.options.signal_stack_size orelse return; | 1693 | const size = std.options.signal_stack_size orelse return; |