| ... | ... | @@ -1,8 +1,8 @@ |
| 1 | 1 | const std = @import("std.zig"); |
| 2 | 2 | const builtin = @import("builtin"); |
| 3 | 3 | const testing = std.testing; |
| 4 | const SpinLock = std.SpinLock; |
| 4 | 5 | const assert = std.debug.assert; |
| 5 | | const Backoff = std.SpinLock.Backoff; |
| 6 | 6 | const c = std.c; |
| 7 | 7 | const os = std.os; |
| 8 | 8 | const time = std.time; |
| ... | ... | @@ -14,13 +14,17 @@ const windows = os.windows; |
| 14 | 14 | pub const ResetEvent = struct { |
| 15 | 15 | os_event: OsEvent, |
| 16 | 16 | |
| 17 | pub const OsEvent = if (builtin.single_threaded) DebugEvent else switch (builtin.os) { |
| 18 | .windows => AtomicEvent, |
| 19 | else => if (builtin.link_libc) PosixEvent else AtomicEvent, |
| 20 | }; |
| 21 | |
| 17 | 22 | pub fn init() ResetEvent { |
| 18 | 23 | return ResetEvent{ .os_event = OsEvent.init() }; |
| 19 | 24 | } |
| 20 | 25 | |
| 21 | 26 | pub fn deinit(self: *ResetEvent) void { |
| 22 | 27 | self.os_event.deinit(); |
| 23 | | self.* = undefined; |
| 24 | 28 | } |
| 25 | 29 | |
| 26 | 30 | /// Returns whether or not the event is currenetly set |
| ... | ... | @@ -29,308 +33,116 @@ pub const ResetEvent = struct { |
| 29 | 33 | } |
| 30 | 34 | |
| 31 | 35 | /// Sets the event if not already set and |
| 32 | | /// wakes up AT LEAST one thread waiting the event. |
| 33 | | /// Returns whether or not a thread was woken up. |
| 34 | | pub fn set(self: *ResetEvent, auto_reset: bool) bool { |
| 35 | | return self.os_event.set(auto_reset); |
| 36 | /// wakes up at least one thread waiting the event. |
| 37 | pub fn set(self: *ResetEvent) void { |
| 38 | return self.os_event.set(); |
| 36 | 39 | } |
| 37 | 40 | |
| 38 | 41 | /// Resets the event to its original, unset state. |
| 39 | | /// Returns whether or not the event was currently set before un-setting. |
| 40 | | pub fn reset(self: *ResetEvent) bool { |
| 42 | pub fn reset(self: *ResetEvent) void { |
| 41 | 43 | return self.os_event.reset(); |
| 42 | 44 | } |
| 43 | 45 | |
| 44 | | const WaitError = error{ |
| 45 | | /// The thread blocked longer than the maximum time specified. |
| 46 | | TimedOut, |
| 47 | | }; |
| 46 | /// Wait for the event to be set by blocking the current thread. |
| 47 | pub fn wait(self: *ResetEvent) void { |
| 48 | return self.os_event.wait(null) catch unreachable; |
| 49 | } |
| 48 | 50 | |
| 49 | 51 | /// Wait for the event to be set by blocking the current thread. |
| 50 | | /// Optionally provided timeout in nanoseconds which throws an |
| 51 | | /// `error.TimedOut` if the thread blocked AT LEAST longer than specified. |
| 52 | | /// Returns whether or not the thread blocked from the event being unset at the time of calling. |
| 53 | | pub fn wait(self: *ResetEvent, timeout_ns: ?u64) WaitError!bool { |
| 52 | /// A timeout in nanoseconds can be provided as a hint for how |
| 53 | /// long the thread should block on the unset event before throwind error.TimedOut. |
| 54 | pub fn timedWait(self: *ResetEvent, timeout_ns: u64) !void { |
| 54 | 55 | return self.os_event.wait(timeout_ns); |
| 55 | 56 | } |
| 56 | 57 | }; |
| 57 | 58 | |
| 58 | | const OsEvent = if (builtin.single_threaded) DebugEvent else switch (builtin.os) { |
| 59 | | .windows => WindowsEvent, |
| 60 | | .linux => if (builtin.link_libc) PosixEvent else LinuxEvent, |
| 61 | | else => if (builtin.link_libc) PosixEvent else SpinEvent, |
| 62 | | }; |
| 63 | | |
| 64 | 59 | const DebugEvent = struct { |
| 65 | | is_set: @TypeOf(set_init), |
| 60 | is_set: bool, |
| 66 | 61 | |
| 67 | | const set_init = if (std.debug.runtime_safety) false else {}; |
| 68 | | |
| 69 | | pub fn init() DebugEvent { |
| 70 | | return DebugEvent{ .is_set = set_init }; |
| 62 | fn init() DebugEvent { |
| 63 | return DebugEvent{ .is_set = false }; |
| 71 | 64 | } |
| 72 | 65 | |
| 73 | | pub fn deinit(self: *DebugEvent) void { |
| 66 | fn deinit(self: *DebugEvent) void { |
| 74 | 67 | self.* = undefined; |
| 75 | 68 | } |
| 76 | 69 | |
| 77 | | pub fn isSet(self: *DebugEvent) bool { |
| 78 | | if (!std.debug.runtime_safety) |
| 79 | | return true; |
| 70 | fn isSet(self: *DebugEvent) bool { |
| 80 | 71 | return self.is_set; |
| 81 | 72 | } |
| 82 | 73 | |
| 83 | | pub fn set(self: *DebugEvent, auto_reset: bool) bool { |
| 84 | | if (std.debug.runtime_safety) |
| 85 | | self.is_set = !auto_reset; |
| 86 | | return false; |
| 87 | | } |
| 88 | | |
| 89 | | pub fn reset(self: *DebugEvent) bool { |
| 90 | | if (!std.debug.runtime_safety) |
| 91 | | return false; |
| 92 | | const was_set = self.is_set; |
| 74 | fn reset(self: *DebugEvent) void { |
| 93 | 75 | self.is_set = false; |
| 94 | | return was_set; |
| 95 | 76 | } |
| 96 | 77 | |
| 97 | | pub fn wait(self: *DebugEvent, timeout: ?u64) ResetEvent.WaitError!bool { |
| 98 | | if (std.debug.runtime_safety and !self.is_set) |
| 99 | | @panic("deadlock detected"); |
| 100 | | return ResetEvent.WaitError.TimedOut; |
| 78 | fn set(self: *DebugEvent) void { |
| 79 | self.is_set = true; |
| 101 | 80 | } |
| 102 | | }; |
| 103 | | |
| 104 | | fn AtomicEvent(comptime FutexImpl: type) type { |
| 105 | | return struct { |
| 106 | | state: u32, |
| 107 | | |
| 108 | | const IS_SET: u32 = 1 << 0; |
| 109 | | const WAIT_MASK = ~IS_SET; |
| 110 | | |
| 111 | | pub const Self = @This(); |
| 112 | | pub const Futex = FutexImpl; |
| 113 | | |
| 114 | | pub fn init() Self { |
| 115 | | return Self{ .state = 0 }; |
| 116 | | } |
| 117 | | |
| 118 | | pub fn deinit(self: *Self) void { |
| 119 | | self.* = undefined; |
| 120 | | } |
| 121 | 81 | |
| 122 | | pub fn isSet(self: *const Self) bool { |
| 123 | | const state = @atomicLoad(u32, &self.state, .Acquire); |
| 124 | | return (state & IS_SET) != 0; |
| 125 | | } |
| 126 | | |
| 127 | | pub fn reset(self: *Self) bool { |
| 128 | | const old_state = @atomicRmw(u32, &self.state, .Xchg, 0, .Monotonic); |
| 129 | | return (old_state & IS_SET) != 0; |
| 130 | | } |
| 131 | | |
| 132 | | pub fn set(self: *Self, auto_reset: bool) bool { |
| 133 | | const new_state = if (auto_reset) 0 else IS_SET; |
| 134 | | const old_state = @atomicRmw(u32, &self.state, .Xchg, new_state, .Release); |
| 135 | | if ((old_state & WAIT_MASK) == 0) { |
| 136 | | return false; |
| 137 | | } |
| 138 | | |
| 139 | | Futex.wake(&self.state); |
| 140 | | return true; |
| 141 | | } |
| 142 | | |
| 143 | | pub fn wait(self: *Self, timeout: ?u64) ResetEvent.WaitError!bool { |
| 144 | | var dummy_value: u32 = undefined; |
| 145 | | const wait_token = @truncate(u32, @ptrToInt(&dummy_value)); |
| 146 | | |
| 147 | | var state = @atomicLoad(u32, &self.state, .Monotonic); |
| 148 | | while (true) { |
| 149 | | if ((state & IS_SET) != 0) |
| 150 | | return false; |
| 151 | | state = @cmpxchgWeak(u32, &self.state, state, wait_token, .Acquire, .Monotonic) orelse break; |
| 152 | | } |
| 153 | | |
| 154 | | try Futex.wait(&self.state, wait_token, timeout); |
| 155 | | return true; |
| 156 | | } |
| 157 | | }; |
| 158 | | } |
| 159 | | |
| 160 | | const SpinEvent = AtomicEvent(struct { |
| 161 | | fn wake(ptr: *const u32) void {} |
| 162 | | |
| 163 | | fn wait(ptr: *const u32, expected: u32, timeout: ?u64) ResetEvent.WaitError!void { |
| 164 | | // TODO: handle platforms where time.Timer.start() fails |
| 165 | | var spin = Backoff.init(); |
| 166 | | var timer = if (timeout == null) null else time.Timer.start() catch unreachable; |
| 167 | | while (@atomicLoad(u32, ptr, .Acquire) == expected) { |
| 168 | | spin.yield(); |
| 169 | | if (timeout) |timeout_ns| { |
| 170 | | if (timer.?.read() > timeout_ns) |
| 171 | | return ResetEvent.WaitError.TimedOut; |
| 172 | | } |
| 173 | | } |
| 82 | fn wait(self: *DebugEvent, timeout: ?u64) !void { |
| 83 | if (self.is_set) |
| 84 | return; |
| 85 | if (timeout != null) |
| 86 | return error.TimedOut; |
| 87 | @panic("deadlock detected"); |
| 174 | 88 | } |
| 175 | | }); |
| 176 | | |
| 177 | | const LinuxEvent = AtomicEvent(struct { |
| 178 | | fn wake(ptr: *const u32) void { |
| 179 | | const key = @ptrCast(*const i32, ptr); |
| 180 | | const rc = linux.futex_wake(key, linux.FUTEX_WAKE | linux.FUTEX_PRIVATE_FLAG, 1); |
| 181 | | assert(linux.getErrno(rc) == 0); |
| 182 | | } |
| 183 | | |
| 184 | | fn wait(ptr: *const u32, expected: u32, timeout: ?u64) ResetEvent.WaitError!void { |
| 185 | | var ts: linux.timespec = undefined; |
| 186 | | var ts_ptr: ?*linux.timespec = null; |
| 187 | | if (timeout) |timeout_ns| { |
| 188 | | ts_ptr = &ts; |
| 189 | | ts.tv_sec = @intCast(isize, timeout_ns / time.ns_per_s); |
| 190 | | ts.tv_nsec = @intCast(isize, timeout_ns % time.ns_per_s); |
| 191 | | } |
| 192 | | |
| 193 | | const key = @ptrCast(*const i32, ptr); |
| 194 | | const key_expect = @bitCast(i32, expected); |
| 195 | | while (@atomicLoad(i32, key, .Acquire) == key_expect) { |
| 196 | | const rc = linux.futex_wait(key, linux.FUTEX_WAIT | linux.FUTEX_PRIVATE_FLAG, key_expect, ts_ptr); |
| 197 | | switch (linux.getErrno(rc)) { |
| 198 | | 0, linux.EAGAIN => break, |
| 199 | | linux.EINTR => continue, |
| 200 | | linux.ETIMEDOUT => return ResetEvent.WaitError.TimedOut, |
| 201 | | else => unreachable, |
| 202 | | } |
| 203 | | } |
| 204 | | } |
| 205 | | }); |
| 206 | | |
| 207 | | const WindowsEvent = AtomicEvent(struct { |
| 208 | | fn wake(ptr: *const u32) void { |
| 209 | | if (getEventHandle()) |handle| { |
| 210 | | const key = @ptrCast(*const c_void, ptr); |
| 211 | | const rc = windows.ntdll.NtReleaseKeyedEvent(handle, key, windows.FALSE, null); |
| 212 | | assert(rc == 0); |
| 213 | | } |
| 214 | | } |
| 215 | | |
| 216 | | fn wait(ptr: *const u32, expected: u32, timeout: ?u64) ResetEvent.WaitError!void { |
| 217 | | // fallback to spinlock if NT Keyed Events arent available |
| 218 | | const handle = getEventHandle() orelse { |
| 219 | | return SpinEvent.Futex.wait(ptr, expected, timeout); |
| 220 | | }; |
| 221 | | |
| 222 | | // NT uses timeouts in units of 100ns with negative value being relative |
| 223 | | var timeout_ptr: ?*windows.LARGE_INTEGER = null; |
| 224 | | var timeout_value: windows.LARGE_INTEGER = undefined; |
| 225 | | if (timeout) |timeout_ns| { |
| 226 | | timeout_ptr = &timeout_value; |
| 227 | | timeout_value = -@intCast(windows.LARGE_INTEGER, timeout_ns / 100); |
| 228 | | } |
| 229 | | |
| 230 | | // NtWaitForKeyedEvent doesnt have spurious wake-ups |
| 231 | | if (@atomicLoad(u32, ptr, .Acquire) == expected) { |
| 232 | | const key = @ptrCast(*const c_void, ptr); |
| 233 | | const rc = windows.ntdll.NtWaitForKeyedEvent(handle, key, windows.FALSE, timeout_ptr); |
| 234 | | switch (rc) { |
| 235 | | 0 => {}, |
| 236 | | windows.WAIT_TIMEOUT => return ResetEvent.WaitError.TimedOut, |
| 237 | | else => unreachable, |
| 238 | | } |
| 239 | | } |
| 240 | | } |
| 241 | | |
| 242 | | var keyed_state = State.Uninitialized; |
| 243 | | var keyed_handle: ?windows.HANDLE = null; |
| 244 | | |
| 245 | | const State = enum(u8) { |
| 246 | | Uninitialized, |
| 247 | | Intializing, |
| 248 | | Initialized, |
| 249 | | }; |
| 250 | | |
| 251 | | fn getEventHandle() ?windows.HANDLE { |
| 252 | | var spin = Backoff.init(); |
| 253 | | var state = @atomicLoad(State, &keyed_state, .Monotonic); |
| 254 | | |
| 255 | | while (true) { |
| 256 | | switch (state) { |
| 257 | | .Initialized => { |
| 258 | | return keyed_handle; |
| 259 | | }, |
| 260 | | .Intializing => { |
| 261 | | spin.yield(); |
| 262 | | state = @atomicLoad(State, &keyed_state, .Acquire); |
| 263 | | }, |
| 264 | | .Uninitialized => state = @cmpxchgWeak(State, &keyed_state, state, .Intializing, .Acquire, .Monotonic) orelse { |
| 265 | | var handle: windows.HANDLE = undefined; |
| 266 | | const access_mask = windows.GENERIC_READ | windows.GENERIC_WRITE; |
| 267 | | if (windows.ntdll.NtCreateKeyedEvent(&handle, access_mask, null, 0) == 0) |
| 268 | | keyed_handle = handle; |
| 269 | | @atomicStore(State, &keyed_state, .Initialized, .Release); |
| 270 | | return keyed_handle; |
| 271 | | }, |
| 272 | | } |
| 273 | | } |
| 274 | | } |
| 275 | | }); |
| 89 | }; |
| 276 | 90 | |
| 277 | 91 | const PosixEvent = struct { |
| 278 | | state: u32, |
| 92 | is_set: bool, |
| 279 | 93 | cond: c.pthread_cond_t, |
| 280 | 94 | mutex: c.pthread_mutex_t, |
| 281 | 95 | |
| 282 | | const IS_SET: u32 = 1; |
| 283 | | |
| 284 | | pub fn init() PosixEvent { |
| 96 | fn init() PosixEvent { |
| 285 | 97 | return PosixEvent{ |
| 286 | | .state = 0, |
| 98 | .is_set = false, |
| 287 | 99 | .cond = c.PTHREAD_COND_INITIALIZER, |
| 288 | 100 | .mutex = c.PTHREAD_MUTEX_INITIALIZER, |
| 289 | 101 | }; |
| 290 | 102 | } |
| 291 | 103 | |
| 292 | | pub fn deinit(self: *PosixEvent) void { |
| 293 | | // On dragonfly, the destroy functions return EINVAL if they were initialized statically. |
| 104 | fn deinit(self: *PosixEvent) void { |
| 105 | // on dragonfly, *destroy() functions can return EINVAL |
| 106 | // for statically initialized pthread structures |
| 107 | const err = if (builtin.os == .dragonfly) os.EINVAL else 0; |
| 108 | |
| 294 | 109 | const retm = c.pthread_mutex_destroy(&self.mutex); |
| 295 | | assert(retm == 0 or retm == (if (builtin.os == .dragonfly) os.EINVAL else 0)); |
| 110 | assert(retm == 0 or retm == err); |
| 296 | 111 | const retc = c.pthread_cond_destroy(&self.cond); |
| 297 | | assert(retc == 0 or retc == (if (builtin.os == .dragonfly) os.EINVAL else 0)); |
| 112 | assert(retc == 0 or retc == err); |
| 298 | 113 | } |
| 299 | 114 | |
| 300 | | pub fn isSet(self: *PosixEvent) bool { |
| 115 | fn isSet(self: *PosixEvent) bool { |
| 301 | 116 | assert(c.pthread_mutex_lock(&self.mutex) == 0); |
| 302 | 117 | defer assert(c.pthread_mutex_unlock(&self.mutex) == 0); |
| 303 | 118 | |
| 304 | | return self.state == IS_SET; |
| 119 | return self.is_set; |
| 305 | 120 | } |
| 306 | 121 | |
| 307 | | pub fn reset(self: *PosixEvent) bool { |
| 122 | fn reset(self: *PosixEvent) void { |
| 308 | 123 | assert(c.pthread_mutex_lock(&self.mutex) == 0); |
| 309 | 124 | defer assert(c.pthread_mutex_unlock(&self.mutex) == 0); |
| 310 | 125 | |
| 311 | | const was_set = self.state == IS_SET; |
| 312 | | self.state = 0; |
| 313 | | return was_set; |
| 126 | self.is_set = false; |
| 314 | 127 | } |
| 315 | 128 | |
| 316 | | pub fn set(self: *PosixEvent, auto_reset: bool) bool { |
| 129 | fn set(self: *PosixEvent) void { |
| 317 | 130 | assert(c.pthread_mutex_lock(&self.mutex) == 0); |
| 318 | 131 | defer assert(c.pthread_mutex_unlock(&self.mutex) == 0); |
| 319 | 132 | |
| 320 | | const had_waiter = self.state > IS_SET; |
| 321 | | self.state = if (auto_reset) 0 else IS_SET; |
| 322 | | if (had_waiter) { |
| 133 | if (!self.is_set) { |
| 134 | self.is_set = true; |
| 323 | 135 | assert(c.pthread_cond_signal(&self.cond) == 0); |
| 324 | 136 | } |
| 325 | | return had_waiter; |
| 326 | 137 | } |
| 327 | 138 | |
| 328 | | pub fn wait(self: *PosixEvent, timeout: ?u64) ResetEvent.WaitError!bool { |
| 139 | fn wait(self: *PosixEvent, timeout: ?u64) !void { |
| 329 | 140 | assert(c.pthread_mutex_lock(&self.mutex) == 0); |
| 330 | 141 | defer assert(c.pthread_mutex_unlock(&self.mutex) == 0); |
| 331 | 142 | |
| 332 | | if (self.state == IS_SET) |
| 333 | | return false; |
| 143 | // quick guard before possibly calling time syscalls below |
| 144 | if (self.is_set) |
| 145 | return; |
| 334 | 146 | |
| 335 | 147 | var ts: os.timespec = undefined; |
| 336 | 148 | if (timeout) |timeout_ns| { |
| ... | ... | @@ -349,85 +161,251 @@ const PosixEvent = struct { |
| 349 | 161 | ts.tv_nsec = @intCast(@TypeOf(ts.tv_nsec), @mod(timeout_abs, time.second)); |
| 350 | 162 | } |
| 351 | 163 | |
| 352 | | var dummy_value: u32 = undefined; |
| 353 | | var wait_token = @truncate(u32, @ptrToInt(&dummy_value)); |
| 354 | | self.state = wait_token; |
| 355 | | |
| 356 | | while (self.state == wait_token) { |
| 164 | while (!self.is_set) { |
| 357 | 165 | const rc = switch (timeout == null) { |
| 358 | 166 | true => c.pthread_cond_wait(&self.cond, &self.mutex), |
| 359 | 167 | else => c.pthread_cond_timedwait(&self.cond, &self.mutex, &ts), |
| 360 | 168 | }; |
| 361 | | // TODO: rc appears to be the positive error code making os.errno() always return 0 on linux |
| 362 | | switch (std.math.max(@as(c_int, os.errno(rc)), rc)) { |
| 169 | switch (rc) { |
| 363 | 170 | 0 => {}, |
| 364 | | os.ETIMEDOUT => return ResetEvent.WaitError.TimedOut, |
| 171 | os.ETIMEDOUT => return error.TimedOut, |
| 365 | 172 | os.EINVAL => unreachable, |
| 366 | 173 | os.EPERM => unreachable, |
| 367 | 174 | else => unreachable, |
| 368 | 175 | } |
| 369 | 176 | } |
| 370 | | return true; |
| 371 | 177 | } |
| 372 | 178 | }; |
| 373 | 179 | |
| 374 | | test "std.ResetEvent" { |
| 375 | | // TODO |
| 376 | | if (builtin.single_threaded) |
| 377 | | return error.SkipZigTest; |
| 180 | const AtomicEvent = struct { |
| 181 | state: State, |
| 182 | |
| 183 | const State = enum(i32) { |
| 184 | Empty, |
| 185 | Waiting, |
| 186 | Signaled, |
| 187 | }; |
| 188 | |
| 189 | fn init() AtomicEvent { |
| 190 | return AtomicEvent{ .state = .Empty }; |
| 191 | } |
| 192 | |
| 193 | fn deinit(self: *AtomicEvent) void { |
| 194 | self.* = undefined; |
| 195 | } |
| 196 | |
| 197 | fn isSet(self: *AtomicEvent) bool { |
| 198 | return @atomicLoad(State, &self.state, .Acquire) == .Signaled; |
| 199 | } |
| 200 | |
| 201 | fn reset(self: *AtomicEvent) void { |
| 202 | @atomicStore(State, &self.state, .Empty, .Monotonic); |
| 203 | } |
| 204 | |
| 205 | fn set(self: *AtomicEvent) void { |
| 206 | if (@atomicRmw(State, &self.state, .Xchg, .Signaled, .Release) == .Waiting) |
| 207 | Futex.wake(@ptrCast(*i32, &self.state)); |
| 208 | } |
| 209 | |
| 210 | fn wait(self: *AtomicEvent, timeout: ?u64) !void { |
| 211 | var state = @atomicLoad(State, &self.state, .Monotonic); |
| 212 | while (state == .Empty) { |
| 213 | state = @cmpxchgWeak(State, &self.state, .Empty, .Waiting, .Acquire, .Monotonic) orelse |
| 214 | return Futex.wait(@ptrCast(*i32, &self.state), @enumToInt(State.Waiting), timeout); |
| 215 | } |
| 216 | } |
| 217 | |
| 218 | pub const Futex = switch (builtin.os) { |
| 219 | .windows => WindowsFutex, |
| 220 | .linux => LinuxFutex, |
| 221 | else => SpinFutex, |
| 222 | }; |
| 223 | |
| 224 | const SpinFutex = struct { |
| 225 | fn wake(ptr: *i32) void {} |
| 226 | |
| 227 | fn wait(ptr: *i32, expected: i32, timeout: ?u64) !void { |
| 228 | // TODO: handle platforms where a monotonic timer isnt available |
| 229 | var timer: time.Timer = undefined; |
| 230 | if (timeout != null) |
| 231 | timer = time.Timer.start() catch unreachable; |
| 232 | |
| 233 | while (@atomicLoad(i32, ptr, .Acquire) == expected) { |
| 234 | switch (builtin.os) { |
| 235 | .windows => SpinLock.yield(400), |
| 236 | else => os.sched_yield() catch SpinLock.yield(1), |
| 237 | } |
| 238 | if (timeout) |timeout_ns| { |
| 239 | if (timer.read() >= timeout_ns) |
| 240 | return error.TimedOut; |
| 241 | } |
| 242 | } |
| 243 | } |
| 244 | }; |
| 245 | |
| 246 | const LinuxFutex = struct { |
| 247 | fn wake(ptr: *i32) void { |
| 248 | const rc = linux.futex_wake(ptr, linux.FUTEX_WAKE | linux.FUTEX_PRIVATE_FLAG, 1); |
| 249 | assert(linux.getErrno(rc) == 0); |
| 250 | } |
| 251 | |
| 252 | fn wait(ptr: *i32, expected: i32, timeout: ?u64) !void { |
| 253 | var ts: linux.timespec = undefined; |
| 254 | var ts_ptr: ?*linux.timespec = null; |
| 255 | if (timeout) |timeout_ns| { |
| 256 | ts_ptr = &ts; |
| 257 | ts.tv_sec = @intCast(isize, timeout_ns / time.ns_per_s); |
| 258 | ts.tv_nsec = @intCast(isize, timeout_ns % time.ns_per_s); |
| 259 | } |
| 260 | |
| 261 | while (@atomicLoad(i32, ptr, .Acquire) == expected) { |
| 262 | const rc = linux.futex_wait(ptr, linux.FUTEX_WAIT | linux.FUTEX_PRIVATE_FLAG, expected, ts_ptr); |
| 263 | switch (linux.getErrno(rc)) { |
| 264 | 0 => continue, |
| 265 | os.ETIMEDOUT => return error.TimedOut, |
| 266 | os.EINTR => continue, |
| 267 | os.EAGAIN => return, |
| 268 | else => unreachable, |
| 269 | } |
| 270 | } |
| 271 | } |
| 272 | }; |
| 273 | |
| 274 | const WindowsFutex = struct { |
| 275 | pub fn wake(ptr: *i32) void { |
| 276 | const handle = getEventHandle() orelse return SpinFutex.wake(ptr); |
| 277 | const key = @ptrCast(*const c_void, ptr); |
| 278 | const rc = windows.ntdll.NtReleaseKeyedEvent(handle, key, windows.FALSE, null); |
| 279 | assert(rc == 0); |
| 280 | } |
| 281 | |
| 282 | pub fn wait(ptr: *i32, expected: i32, timeout: ?u64) !void { |
| 283 | const handle = getEventHandle() orelse return SpinFutex.wait(ptr, expected, timeout); |
| 284 | |
| 285 | // NT uses timeouts in units of 100ns with negative value being relative |
| 286 | var timeout_ptr: ?*windows.LARGE_INTEGER = null; |
| 287 | var timeout_value: windows.LARGE_INTEGER = undefined; |
| 288 | if (timeout) |timeout_ns| { |
| 289 | timeout_ptr = &timeout_value; |
| 290 | timeout_value = -@intCast(windows.LARGE_INTEGER, timeout_ns / 100); |
| 291 | } |
| 292 | |
| 293 | // NtWaitForKeyedEvent doesnt have spurious wake-ups |
| 294 | const key = @ptrCast(*const c_void, ptr); |
| 295 | const rc = windows.ntdll.NtWaitForKeyedEvent(handle, key, windows.FALSE, timeout_ptr); |
| 296 | switch (rc) { |
| 297 | windows.WAIT_TIMEOUT => return error.TimedOut, |
| 298 | windows.WAIT_OBJECT_0 => {}, |
| 299 | else => unreachable, |
| 300 | } |
| 301 | } |
| 378 | 302 | |
| 303 | var event_handle: usize = EMPTY; |
| 304 | const EMPTY = ~@as(usize, 0); |
| 305 | const LOADING = EMPTY - 1; |
| 306 | |
| 307 | pub fn getEventHandle() ?windows.HANDLE { |
| 308 | var handle = @atomicLoad(usize, &event_handle, .Monotonic); |
| 309 | while (true) { |
| 310 | switch (handle) { |
| 311 | EMPTY => handle = @cmpxchgWeak(usize, &event_handle, EMPTY, LOADING, .Acquire, .Monotonic) orelse { |
| 312 | const handle_ptr = @ptrCast(*windows.HANDLE, &handle); |
| 313 | const access_mask = windows.GENERIC_READ | windows.GENERIC_WRITE; |
| 314 | if (windows.ntdll.NtCreateKeyedEvent(handle_ptr, access_mask, null, 0) != 0) |
| 315 | handle = 0; |
| 316 | @atomicStore(usize, &event_handle, handle, .Monotonic); |
| 317 | return @intToPtr(?windows.HANDLE, handle); |
| 318 | }, |
| 319 | LOADING => { |
| 320 | SpinLock.yield(1000); |
| 321 | handle = @atomicLoad(usize, &event_handle, .Monotonic); |
| 322 | }, |
| 323 | else => { |
| 324 | return @intToPtr(?windows.HANDLE, handle); |
| 325 | }, |
| 326 | } |
| 327 | } |
| 328 | } |
| 329 | }; |
| 330 | }; |
| 331 | |
| 332 | test "std.ResetEvent" { |
| 379 | 333 | var event = ResetEvent.init(); |
| 380 | 334 | defer event.deinit(); |
| 381 | 335 | |
| 382 | 336 | // test event setting |
| 383 | 337 | testing.expect(event.isSet() == false); |
| 384 | | testing.expect(event.set(false) == false); |
| 338 | event.set(); |
| 385 | 339 | testing.expect(event.isSet() == true); |
| 386 | 340 | |
| 387 | 341 | // test event resetting |
| 388 | | testing.expect(event.reset() == true); |
| 342 | event.reset(); |
| 389 | 343 | testing.expect(event.isSet() == false); |
| 390 | | testing.expect(event.reset() == false); |
| 391 | 344 | |
| 392 | | // test cross thread signaling |
| 393 | | const Context = struct { |
| 394 | | event: ResetEvent, |
| 395 | | value: u128, |
| 345 | // test event waiting (non-blocking) |
| 346 | event.set(); |
| 347 | event.wait(); |
| 348 | try event.timedWait(1); |
| 396 | 349 | |
| 397 | | fn receiver(self: *@This()) void { |
| 398 | | // wait for the sender to notify us with updated value |
| 399 | | assert(self.value == 0); |
| 400 | | assert((self.event.wait(1 * time.second) catch unreachable) == true); |
| 401 | | assert(self.value == 1); |
| 350 | // test cross-thread signaling |
| 351 | if (builtin.single_threaded) |
| 352 | return; |
| 402 | 353 | |
| 403 | | // wait for sender to sleep, then notify it of new value |
| 404 | | time.sleep(50 * time.millisecond); |
| 405 | | self.value = 2; |
| 406 | | assert(self.event.set(false) == true); |
| 354 | const Context = struct { |
| 355 | const Self = @This(); |
| 356 | |
| 357 | value: u128, |
| 358 | in: ResetEvent, |
| 359 | out: ResetEvent, |
| 360 | |
| 361 | fn init() Self { |
| 362 | return Self{ |
| 363 | .value = 0, |
| 364 | .in = ResetEvent.init(), |
| 365 | .out = ResetEvent.init(), |
| 366 | }; |
| 407 | 367 | } |
| 408 | 368 | |
| 409 | | fn sender(self: *@This()) !void { |
| 410 | | // wait for the receiver() to start wait()'ing |
| 411 | | time.sleep(50 * time.millisecond); |
| 369 | fn deinit(self: *Self) void { |
| 370 | self.in.deinit(); |
| 371 | self.out.deinit(); |
| 372 | self.* = undefined; |
| 373 | } |
| 412 | 374 | |
| 413 | | // update value to 1 and notify the receiver() |
| 414 | | assert(self.value == 0); |
| 375 | fn sender(self: *Self) void { |
| 376 | // update value and signal input |
| 377 | testing.expect(self.value == 0); |
| 415 | 378 | self.value = 1; |
| 416 | | assert(self.event.set(true) == true); |
| 417 | | |
| 418 | | // wait for the receiver to update the value & notify us |
| 419 | | assert((try self.event.wait(1 * time.second)) == true); |
| 420 | | assert(self.value == 2); |
| 379 | self.in.set(); |
| 380 | |
| 381 | // wait for receiver to update value and signal output |
| 382 | self.out.wait(); |
| 383 | testing.expect(self.value == 2); |
| 384 | |
| 385 | // update value and signal final input |
| 386 | self.value = 3; |
| 387 | self.in.set(); |
| 421 | 388 | } |
| 422 | | }; |
| 423 | 389 | |
| 424 | | _ = event.reset(); |
| 425 | | var context = Context{ |
| 426 | | .event = event, |
| 427 | | .value = 0, |
| 390 | fn receiver(self: *Self) void { |
| 391 | // wait for sender to update value and signal input |
| 392 | self.in.wait(); |
| 393 | assert(self.value == 1); |
| 394 | |
| 395 | // update value and signal output |
| 396 | self.in.reset(); |
| 397 | self.value = 2; |
| 398 | self.out.set(); |
| 399 | |
| 400 | // wait for sender to update value and signal final input |
| 401 | self.in.wait(); |
| 402 | assert(self.value == 3); |
| 403 | } |
| 428 | 404 | }; |
| 429 | 405 | |
| 430 | | var receiver = try std.Thread.spawn(&context, Context.receiver); |
| 406 | var context = Context.init(); |
| 407 | defer context.deinit(); |
| 408 | const receiver = try std.Thread.spawn(&context, Context.receiver); |
| 431 | 409 | defer receiver.wait(); |
| 432 | | try context.sender(); |
| 410 | context.sender(); |
| 433 | 411 | } |