authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-27 21:17:44-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-29 06:20:52-07:00
loga28d3059e60ccbed2b2b159c41899488330bc671
tree23ec1390a300a498e24e191cf2b1d5dfafb46816
parent30448d92af596724dc227c96803961bc41de9253

std.Io.Threaded: implement ResetEvent in terms of pthreads

needed for NetBSD

2 files changed, 123 insertions(+), 34 deletions(-)

lib/std/Io/Threaded.zig+121-32
...@@ -5787,7 +5787,13 @@ pub fn futexWake(ptr: *const std.atomic.Value(u32), max_waiters: u32) void {...@@ -5787,7 +5787,13 @@ pub fn futexWake(ptr: *const std.atomic.Value(u32), max_waiters: u32) void {
5787///5787///
5788/// It can also block threads until the value is set with cancelation via timed5788/// It can also block threads until the value is set with cancelation via timed
5789/// waits. Statically initializable; four bytes on all targets.5789/// waits. Statically initializable; four bytes on all targets.
5790pub const ResetEvent = enum(u32) {5790pub const ResetEvent = switch (native_os) {
5791 .netbsd => ResetEventPosix,
5792 else => ResetEventFutex,
5793};
5794
5795/// A `ResetEvent` implementation based on futexes.
5796const ResetEventFutex = enum(u32) {
5791 unset = 0,5797 unset = 0,
5792 waiting = 1,5798 waiting = 1,
5793 is_set = 2,5799 is_set = 2,
...@@ -5798,15 +5804,15 @@ pub const ResetEvent = enum(u32) {...@@ -5798,15 +5804,15 @@ pub const ResetEvent = enum(u32) {
5798 ///5804 ///
5799 /// The memory accesses before the `set` can be said to happen before5805 /// The memory accesses before the `set` can be said to happen before
5800 /// `isSet` returns true.5806 /// `isSet` returns true.
5801 pub fn isSet(re: *const ResetEvent) bool {5807 pub fn isSet(ref: *const ResetEventFutex) bool {
5802 if (builtin.single_threaded) return switch (re.*) {5808 if (builtin.single_threaded) return switch (ref.*) {
5803 .unset => false,5809 .unset => false,
5804 .waiting => unreachable,5810 .waiting => unreachable,
5805 .is_set => true,5811 .is_set => true,
5806 };5812 };
5807 // Acquire barrier ensures memory accesses before `set` happen before5813 // Acquire barrier ensures memory accesses before `set` happen before
5808 // returning true.5814 // returning true.
5809 return @atomicLoad(ResetEvent, re, .acquire) == .is_set;5815 return @atomicLoad(ResetEventFutex, ref, .acquire) == .is_set;
5810 }5816 }
58115817
5812 /// Blocks the calling thread until `set` is called.5818 /// Blocks the calling thread until `set` is called.
...@@ -5814,51 +5820,51 @@ pub const ResetEvent = enum(u32) {...@@ -5814,51 +5820,51 @@ pub const ResetEvent = enum(u32) {
5814 /// This is effectively a more efficient version of `while (!isSet()) {}`.5820 /// This is effectively a more efficient version of `while (!isSet()) {}`.
5815 ///5821 ///
5816 /// The memory accesses before the `set` can be said to happen before `wait` returns.5822 /// The memory accesses before the `set` can be said to happen before `wait` returns.
5817 pub fn wait(re: *ResetEvent, t: *Threaded) Io.Cancelable!void {5823 pub fn wait(ref: *ResetEventFutex, t: *Threaded) Io.Cancelable!void {
5818 if (builtin.single_threaded) switch (re.*) {5824 if (builtin.single_threaded) switch (ref.*) {
5819 .unset => unreachable, // Deadlock, no other threads to wake us up.5825 .unset => unreachable, // Deadlock, no other threads to wake us up.
5820 .waiting => unreachable, // Invalid state.5826 .waiting => unreachable, // Invalid state.
5821 .is_set => return,5827 .is_set => return,
5822 };5828 };
5823 if (re.isSet()) {5829 // Try to set the state from `unset` to `waiting` to indicate to the
5830 // `set` thread that others are blocked on the ResetEventFutex. Avoid using
5831 // any strict barriers until we know the ResetEventFutex is set.
5832 var state = @atomicLoad(ResetEventFutex, ref, .acquire);
5833 if (state == .is_set) {
5824 @branchHint(.likely);5834 @branchHint(.likely);
5825 return;5835 return;
5826 }5836 }
5827 // Try to set the state from `unset` to `waiting` to indicate to the
5828 // `set` thread that others are blocked on the ResetEvent. Avoid using
5829 // any strict barriers until we know the ResetEvent is set.
5830 var state = @atomicLoad(ResetEvent, re, .acquire);
5831 if (state == .unset) {5837 if (state == .unset) {
5832 state = @cmpxchgStrong(ResetEvent, re, state, .waiting, .acquire, .acquire) orelse .waiting;5838 state = @cmpxchgStrong(ResetEventFutex, ref, state, .waiting, .acquire, .acquire) orelse .waiting;
5833 }5839 }
5834 while (state == .waiting) {5840 while (state == .waiting) {
5835 try futexWait(t, @ptrCast(re), @intFromEnum(ResetEvent.waiting));5841 try futexWait(t, @ptrCast(ref), @intFromEnum(ResetEventFutex.waiting));
5836 state = @atomicLoad(ResetEvent, re, .acquire);5842 state = @atomicLoad(ResetEventFutex, ref, .acquire);
5837 }5843 }
5838 assert(state == .is_set);5844 assert(state == .is_set);
5839 }5845 }
58405846
5841 /// Same as `wait` except uninterruptible.5847 /// Same as `wait` except uninterruptible.
5842 pub fn waitUncancelable(re: *ResetEvent) void {5848 pub fn waitUncancelable(ref: *ResetEventFutex) void {
5843 if (builtin.single_threaded) switch (re.*) {5849 if (builtin.single_threaded) switch (ref.*) {
5844 .unset => unreachable, // Deadlock, no other threads to wake us up.5850 .unset => unreachable, // Deadlock, no other threads to wake us up.
5845 .waiting => unreachable, // Invalid state.5851 .waiting => unreachable, // Invalid state.
5846 .is_set => return,5852 .is_set => return,
5847 };5853 };
5848 if (re.isSet()) {5854 // Try to set the state from `unset` to `waiting` to indicate to the
5855 // `set` thread that others are blocked on the ResetEventFutex. Avoid using
5856 // any strict barriers until we know the ResetEventFutex is set.
5857 var state = @atomicLoad(ResetEventFutex, ref, .acquire);
5858 if (state == .is_set) {
5849 @branchHint(.likely);5859 @branchHint(.likely);
5850 return;5860 return;
5851 }5861 }
5852 // Try to set the state from `unset` to `waiting` to indicate to the
5853 // `set` thread that others are blocked on the ResetEvent. Avoid using
5854 // any strict barriers until we know the ResetEvent is set.
5855 var state = @atomicLoad(ResetEvent, re, .acquire);
5856 if (state == .unset) {5862 if (state == .unset) {
5857 state = @cmpxchgStrong(ResetEvent, re, state, .waiting, .acquire, .acquire) orelse .waiting;5863 state = @cmpxchgStrong(ResetEventFutex, ref, state, .waiting, .acquire, .acquire) orelse .waiting;
5858 }5864 }
5859 while (state == .waiting) {5865 while (state == .waiting) {
5860 futexWaitUncancelable(@ptrCast(re), @intFromEnum(ResetEvent.waiting));5866 futexWaitUncancelable(@ptrCast(ref), @intFromEnum(ResetEventFutex.waiting));
5861 state = @atomicLoad(ResetEvent, re, .acquire);5867 state = @atomicLoad(ResetEventFutex, ref, .acquire);
5862 }5868 }
5863 assert(state == .is_set);5869 assert(state == .is_set);
5864 }5870 }
...@@ -5871,26 +5877,109 @@ pub const ResetEvent = enum(u32) {...@@ -5871,26 +5877,109 @@ pub const ResetEvent = enum(u32) {
5871 ///5877 ///
5872 /// The memory accesses before `set` can be said to happen before `isSet`5878 /// The memory accesses before `set` can be said to happen before `isSet`
5873 /// returns true or `wait`/`timedWait` return successfully.5879 /// returns true or `wait`/`timedWait` return successfully.
5874 pub fn set(re: *ResetEvent) void {5880 pub fn set(ref: *ResetEventFutex) void {
5875 if (builtin.single_threaded) {5881 if (builtin.single_threaded) {
5876 re.* = .is_set;5882 ref.* = .is_set;
5877 return;5883 return;
5878 }5884 }
5879 if (@atomicRmw(ResetEvent, re, .Xchg, .is_set, .release) == .waiting) {5885 if (@atomicRmw(ResetEventFutex, ref, .Xchg, .is_set, .release) == .waiting) {
5880 futexWake(@ptrCast(re), std.math.maxInt(u32));5886 futexWake(@ptrCast(ref), std.math.maxInt(u32));
5881 }5887 }
5882 }5888 }
58835889
5884 /// Unmarks the ResetEvent as if `set` was never called.5890 /// Unmarks the ResetEventFutex as if `set` was never called.
5885 ///5891 ///
5886 /// Assumes no threads are blocked in `wait` or `timedWait`. Concurrent5892 /// Assumes no threads are blocked in `wait` or `timedWait`. Concurrent
5887 /// calls to `set`, `isSet` and `reset` are allowed.5893 /// calls to `set`, `isSet` and `reset` are allowed.
5888 pub fn reset(re: *ResetEvent) void {5894 pub fn reset(ref: *ResetEventFutex) void {
5895 if (builtin.single_threaded) {
5896 ref.* = .unset;
5897 return;
5898 }
5899 @atomicStore(ResetEventFutex, ref, .unset, .monotonic);
5900 }
5901};
5902
5903/// A `ResetEvent` implementation based on pthreads API.
5904const ResetEventPosix = struct {
5905 cond: std.c.pthread_cond_t,
5906 mutex: std.c.pthread_mutex_t,
5907 state: ResetEventFutex,
5908
5909 pub const unset: ResetEventPosix = .{
5910 .cond = std.c.PTHREAD_COND_INITIALIZER,
5911 .mutex = std.c.PTHREAD_MUTEX_INITIALIZER,
5912 .state = .unset,
5913 };
5914
5915 pub fn isSet(rep: *const ResetEventPosix) bool {
5916 if (builtin.single_threaded) return switch (rep.state) {
5917 .unset => false,
5918 .waiting => unreachable,
5919 .is_set => true,
5920 };
5921 return @atomicLoad(ResetEventFutex, &rep.state, .acquire) == .is_set;
5922 }
5923
5924 pub fn wait(rep: *ResetEventPosix, t: *Threaded) Io.Cancelable!void {
5925 if (builtin.single_threaded) switch (rep.*) {
5926 .unset => unreachable, // Deadlock, no other threads to wake us up.
5927 .waiting => unreachable, // Invalid state.
5928 .is_set => return,
5929 };
5930 assert(std.c.pthread_mutex_lock(&rep.mutex) == .SUCCESS);
5931 defer assert(std.c.pthread_mutex_unlock(&rep.mutex) == .SUCCESS);
5932 sw: switch (rep.state) {
5933 .unset => {
5934 rep.state = .waiting;
5935 continue :sw .waiting;
5936 },
5937 .waiting => {
5938 try t.checkCancel();
5939 assert(std.c.pthread_cond_wait(&rep.cond, &rep.mutex) == .SUCCESS);
5940 continue :sw rep.state;
5941 },
5942 .is_set => return,
5943 }
5944 }
5945
5946 pub fn waitUncancelable(rep: *ResetEventPosix) void {
5947 if (builtin.single_threaded) switch (rep.*) {
5948 .unset => unreachable, // Deadlock, no other threads to wake us up.
5949 .waiting => unreachable, // Invalid state.
5950 .is_set => return,
5951 };
5952 assert(std.c.pthread_mutex_lock(&rep.mutex) == .SUCCESS);
5953 defer assert(std.c.pthread_mutex_unlock(&rep.mutex) == .SUCCESS);
5954 sw: switch (rep.state) {
5955 .unset => {
5956 rep.state = .waiting;
5957 continue :sw .waiting;
5958 },
5959 .waiting => {
5960 assert(std.c.pthread_cond_wait(&rep.cond, &rep.mutex) == .SUCCESS);
5961 continue :sw rep.state;
5962 },
5963 .is_set => return,
5964 }
5965 }
5966
5967 pub fn set(rep: *ResetEventPosix) void {
5968 if (builtin.single_threaded) {
5969 rep.* = .is_set;
5970 return;
5971 }
5972 if (@atomicRmw(ResetEventFutex, &rep.state, .Xchg, .is_set, .release) == .waiting) {
5973 assert(std.c.pthread_cond_broadcast(&rep.cond) == .SUCCESS);
5974 }
5975 }
5976
5977 pub fn reset(rep: *ResetEventPosix) void {
5889 if (builtin.single_threaded) {5978 if (builtin.single_threaded) {
5890 re.* = .unset;5979 rep.* = .unset;
5891 return;5980 return;
5892 }5981 }
5893 @atomicStore(ResetEvent, re, .unset, .monotonic);5982 @atomicStore(ResetEventFutex, &rep.state, .unset, .monotonic);
5894 }5983 }
5895};5984};
58965985
lib/std/c.zig+2-2
...@@ -10874,13 +10874,13 @@ pub extern "c" fn dn_expand(...@@ -10874,13 +10874,13 @@ pub extern "c" fn dn_expand(
10874 length: c_int,10874 length: c_int,
10875) c_int;10875) c_int;
1087610876
10877pub const PTHREAD_MUTEX_INITIALIZER = pthread_mutex_t{};10877pub const PTHREAD_MUTEX_INITIALIZER: pthread_mutex_t = .{};
10878pub extern "c" fn pthread_mutex_lock(mutex: *pthread_mutex_t) E;10878pub extern "c" fn pthread_mutex_lock(mutex: *pthread_mutex_t) E;
10879pub extern "c" fn pthread_mutex_unlock(mutex: *pthread_mutex_t) E;10879pub extern "c" fn pthread_mutex_unlock(mutex: *pthread_mutex_t) E;
10880pub extern "c" fn pthread_mutex_trylock(mutex: *pthread_mutex_t) E;10880pub extern "c" fn pthread_mutex_trylock(mutex: *pthread_mutex_t) E;
10881pub extern "c" fn pthread_mutex_destroy(mutex: *pthread_mutex_t) E;10881pub extern "c" fn pthread_mutex_destroy(mutex: *pthread_mutex_t) E;
1088210882
10883pub const PTHREAD_COND_INITIALIZER = pthread_cond_t{};10883pub const PTHREAD_COND_INITIALIZER: pthread_cond_t = .{};
10884pub extern "c" fn pthread_cond_wait(noalias cond: *pthread_cond_t, noalias mutex: *pthread_mutex_t) E;10884pub extern "c" fn pthread_cond_wait(noalias cond: *pthread_cond_t, noalias mutex: *pthread_mutex_t) E;
10885pub extern "c" fn pthread_cond_timedwait(noalias cond: *pthread_cond_t, noalias mutex: *pthread_mutex_t, noalias abstime: *const timespec) E;10885pub extern "c" fn pthread_cond_timedwait(noalias cond: *pthread_cond_t, noalias mutex: *pthread_mutex_t, noalias abstime: *const timespec) E;
10886pub extern "c" fn pthread_cond_signal(cond: *pthread_cond_t) E;10886pub extern "c" fn pthread_cond_signal(cond: *pthread_cond_t) E;