| ... | @@ -1,8 +1,8 @@ | ... | @@ -1,8 +1,8 @@ |
| 1 | const std = @import("std.zig"); | 1 | const std = @import("std.zig"); |
| 2 | const builtin = @import("builtin"); | 2 | const builtin = @import("builtin"); |
| 3 | const testing = std.testing; | 3 | const testing = std.testing; |
| | 4 | const SpinLock = std.SpinLock; |
| 4 | const assert = std.debug.assert; | 5 | const assert = std.debug.assert; |
| 5 | const Backoff = std.SpinLock.Backoff; | | |
| 6 | const c = std.c; | 6 | const c = std.c; |
| 7 | const os = std.os; | 7 | const os = std.os; |
| 8 | const time = std.time; | 8 | const time = std.time; |
| ... | @@ -14,13 +14,17 @@ const windows = os.windows; | ... | @@ -14,13 +14,17 @@ const windows = os.windows; |
| 14 | pub const ResetEvent = struct { | 14 | pub const ResetEvent = struct { |
| 15 | os_event: OsEvent, | 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 | pub fn init() ResetEvent { | 22 | pub fn init() ResetEvent { |
| 18 | return ResetEvent{ .os_event = OsEvent.init() }; | 23 | return ResetEvent{ .os_event = OsEvent.init() }; |
| 19 | } | 24 | } |
| 20 | | 25 | |
| 21 | pub fn deinit(self: *ResetEvent) void { | 26 | pub fn deinit(self: *ResetEvent) void { |
| 22 | self.os_event.deinit(); | 27 | self.os_event.deinit(); |
| 23 | self.* = undefined; | | |
| 24 | } | 28 | } |
| 25 | | 29 | |
| 26 | /// Returns whether or not the event is currenetly set | 30 | /// Returns whether or not the event is currenetly set |
| ... | @@ -29,308 +33,116 @@ pub const ResetEvent = struct { | ... | @@ -29,308 +33,116 @@ pub const ResetEvent = struct { |
| 29 | } | 33 | } |
| 30 | | 34 | |
| 31 | /// Sets the event if not already set and | 35 | /// Sets the event if not already set and |
| 32 | /// wakes up AT LEAST one thread waiting the event. | 36 | /// wakes up at least one thread waiting the event. |
| 33 | /// Returns whether or not a thread was woken up. | 37 | pub fn set(self: *ResetEvent) void { |
| 34 | pub fn set(self: *ResetEvent, auto_reset: bool) bool { | 38 | return self.os_event.set(); |
| 35 | return self.os_event.set(auto_reset); | | |
| 36 | } | 39 | } |
| 37 | | 40 | |
| 38 | /// Resets the event to its original, unset state. | 41 | /// Resets the event to its original, unset state. |
| 39 | /// Returns whether or not the event was currently set before un-setting. | 42 | pub fn reset(self: *ResetEvent) void { |
| 40 | pub fn reset(self: *ResetEvent) bool { | | |
| 41 | return self.os_event.reset(); | 43 | return self.os_event.reset(); |
| 42 | } | 44 | } |
| 43 | | 45 | |
| 44 | const WaitError = error{ | 46 | /// Wait for the event to be set by blocking the current thread. |
| 45 | /// The thread blocked longer than the maximum time specified. | 47 | pub fn wait(self: *ResetEvent) void { |
| 46 | TimedOut, | 48 | return self.os_event.wait(null) catch unreachable; |
| 47 | }; | 49 | } |
| 48 | | 50 | |
| 49 | /// Wait for the event to be set by blocking the current thread. | 51 | /// Wait for the event to be set by blocking the current thread. |
| 50 | /// Optionally provided timeout in nanoseconds which throws an | 52 | /// A timeout in nanoseconds can be provided as a hint for how |
| 51 | /// `error.TimedOut` if the thread blocked AT LEAST longer than specified. | 53 | /// long the thread should block on the unset event before throwind error.TimedOut. |
| 52 | /// Returns whether or not the thread blocked from the event being unset at the time of calling. | 54 | pub fn timedWait(self: *ResetEvent, timeout_ns: u64) !void { |
| 53 | pub fn wait(self: *ResetEvent, timeout_ns: ?u64) WaitError!bool { | | |
| 54 | return self.os_event.wait(timeout_ns); | 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 | const DebugEvent = struct { | 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 {}; | 62 | fn init() DebugEvent { |
| 68 | | 63 | return DebugEvent{ .is_set = false }; |
| 69 | pub fn init() DebugEvent { | | |
| 70 | return DebugEvent{ .is_set = set_init }; | | |
| 71 | } | 64 | } |
| 72 | | 65 | |
| 73 | pub fn deinit(self: *DebugEvent) void { | 66 | fn deinit(self: *DebugEvent) void { |
| 74 | self.* = undefined; | 67 | self.* = undefined; |
| 75 | } | 68 | } |
| 76 | | 69 | |
| 77 | pub fn isSet(self: *DebugEvent) bool { | 70 | fn isSet(self: *DebugEvent) bool { |
| 78 | if (!std.debug.runtime_safety) | | |
| 79 | return true; | | |
| 80 | return self.is_set; | 71 | return self.is_set; |
| 81 | } | 72 | } |
| 82 | | 73 | |
| 83 | pub fn set(self: *DebugEvent, auto_reset: bool) bool { | 74 | fn reset(self: *DebugEvent) void { |
| 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; | | |
| 93 | self.is_set = false; | 75 | self.is_set = false; |
| 94 | return was_set; | | |
| 95 | } | 76 | } |
| 96 | | 77 | |
| 97 | pub fn wait(self: *DebugEvent, timeout: ?u64) ResetEvent.WaitError!bool { | 78 | fn set(self: *DebugEvent) void { |
| 98 | if (std.debug.runtime_safety and !self.is_set) | 79 | self.is_set = true; |
| 99 | @panic("deadlock detected"); | | |
| 100 | return ResetEvent.WaitError.TimedOut; | | |
| 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 { | 82 | fn wait(self: *DebugEvent, timeout: ?u64) !void { |
| 123 | const state = @atomicLoad(u32, &self.state, .Acquire); | 83 | if (self.is_set) |
| 124 | return (state & IS_SET) != 0; | 84 | return; |
| 125 | } | 85 | if (timeout != null) |
| 126 | | 86 | return error.TimedOut; |
| 127 | pub fn reset(self: *Self) bool { | 87 | @panic("deadlock detected"); |
| 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 | } | | |
| 174 | } | 88 | } |
| 175 | }); | 89 | }; |
| 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 | }); | | |
| 276 | | 90 | |
| 277 | const PosixEvent = struct { | 91 | const PosixEvent = struct { |
| 278 | state: u32, | 92 | is_set: bool, |
| 279 | cond: c.pthread_cond_t, | 93 | cond: c.pthread_cond_t, |
| 280 | mutex: c.pthread_mutex_t, | 94 | mutex: c.pthread_mutex_t, |
| 281 | | 95 | |
| 282 | const IS_SET: u32 = 1; | 96 | fn init() PosixEvent { |
| 283 | | | |
| 284 | pub fn init() PosixEvent { | | |
| 285 | return PosixEvent{ | 97 | return PosixEvent{ |
| 286 | .state = 0, | 98 | .is_set = false, |
| 287 | .cond = c.PTHREAD_COND_INITIALIZER, | 99 | .cond = c.PTHREAD_COND_INITIALIZER, |
| 288 | .mutex = c.PTHREAD_MUTEX_INITIALIZER, | 100 | .mutex = c.PTHREAD_MUTEX_INITIALIZER, |
| 289 | }; | 101 | }; |
| 290 | } | 102 | } |
| 291 | | 103 | |
| 292 | pub fn deinit(self: *PosixEvent) void { | 104 | fn deinit(self: *PosixEvent) void { |
| 293 | // On dragonfly, the destroy functions return EINVAL if they were initialized statically. | 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 | const retm = c.pthread_mutex_destroy(&self.mutex); | 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 | const retc = c.pthread_cond_destroy(&self.cond); | 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 | assert(c.pthread_mutex_lock(&self.mutex) == 0); | 116 | assert(c.pthread_mutex_lock(&self.mutex) == 0); |
| 302 | defer assert(c.pthread_mutex_unlock(&self.mutex) == 0); | 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 | assert(c.pthread_mutex_lock(&self.mutex) == 0); | 123 | assert(c.pthread_mutex_lock(&self.mutex) == 0); |
| 309 | defer assert(c.pthread_mutex_unlock(&self.mutex) == 0); | 124 | defer assert(c.pthread_mutex_unlock(&self.mutex) == 0); |
| 310 | | 125 | |
| 311 | const was_set = self.state == IS_SET; | 126 | self.is_set = false; |
| 312 | self.state = 0; | | |
| 313 | return was_set; | | |
| 314 | } | 127 | } |
| 315 | | 128 | |
| 316 | pub fn set(self: *PosixEvent, auto_reset: bool) bool { | 129 | fn set(self: *PosixEvent) void { |
| 317 | assert(c.pthread_mutex_lock(&self.mutex) == 0); | 130 | assert(c.pthread_mutex_lock(&self.mutex) == 0); |
| 318 | defer assert(c.pthread_mutex_unlock(&self.mutex) == 0); | 131 | defer assert(c.pthread_mutex_unlock(&self.mutex) == 0); |
| 319 | | 132 | |
| 320 | const had_waiter = self.state > IS_SET; | 133 | if (!self.is_set) { |
| 321 | self.state = if (auto_reset) 0 else IS_SET; | 134 | self.is_set = true; |
| 322 | if (had_waiter) { | | |
| 323 | assert(c.pthread_cond_signal(&self.cond) == 0); | 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 | assert(c.pthread_mutex_lock(&self.mutex) == 0); | 140 | assert(c.pthread_mutex_lock(&self.mutex) == 0); |
| 330 | defer assert(c.pthread_mutex_unlock(&self.mutex) == 0); | 141 | defer assert(c.pthread_mutex_unlock(&self.mutex) == 0); |
| 331 | | 142 | |
| 332 | if (self.state == IS_SET) | 143 | // quick guard before possibly calling time syscalls below |
| 333 | return false; | 144 | if (self.is_set) |
| | 145 | return; |
| 334 | | 146 | |
| 335 | var ts: os.timespec = undefined; | 147 | var ts: os.timespec = undefined; |
| 336 | if (timeout) |timeout_ns| { | 148 | if (timeout) |timeout_ns| { |
| ... | @@ -349,85 +161,251 @@ const PosixEvent = struct { | ... | @@ -349,85 +161,251 @@ const PosixEvent = struct { |
| 349 | ts.tv_nsec = @intCast(@TypeOf(ts.tv_nsec), @mod(timeout_abs, time.second)); | 161 | ts.tv_nsec = @intCast(@TypeOf(ts.tv_nsec), @mod(timeout_abs, time.second)); |
| 350 | } | 162 | } |
| 351 | | 163 | |
| 352 | var dummy_value: u32 = undefined; | 164 | while (!self.is_set) { |
| 353 | var wait_token = @truncate(u32, @ptrToInt(&dummy_value)); | | |
| 354 | self.state = wait_token; | | |
| 355 | | | |
| 356 | while (self.state == wait_token) { | | |
| 357 | const rc = switch (timeout == null) { | 165 | const rc = switch (timeout == null) { |
| 358 | true => c.pthread_cond_wait(&self.cond, &self.mutex), | 166 | true => c.pthread_cond_wait(&self.cond, &self.mutex), |
| 359 | else => c.pthread_cond_timedwait(&self.cond, &self.mutex, &ts), | 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 | 169 | switch (rc) { |
| 362 | switch (std.math.max(@as(c_int, os.errno(rc)), rc)) { | | |
| 363 | 0 => {}, | 170 | 0 => {}, |
| 364 | os.ETIMEDOUT => return ResetEvent.WaitError.TimedOut, | 171 | os.ETIMEDOUT => return error.TimedOut, |
| 365 | os.EINVAL => unreachable, | 172 | os.EINVAL => unreachable, |
| 366 | os.EPERM => unreachable, | 173 | os.EPERM => unreachable, |
| 367 | else => unreachable, | 174 | else => unreachable, |
| 368 | } | 175 | } |
| 369 | } | 176 | } |
| 370 | return true; | | |
| 371 | } | 177 | } |
| 372 | }; | 178 | }; |
| 373 | | 179 | |
| 374 | test "std.ResetEvent" { | 180 | const AtomicEvent = struct { |
| 375 | // TODO | 181 | state: State, |
| 376 | if (builtin.single_threaded) | 182 | |
| 377 | return error.SkipZigTest; | 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 | var event = ResetEvent.init(); | 333 | var event = ResetEvent.init(); |
| 380 | defer event.deinit(); | 334 | defer event.deinit(); |
| 381 | | 335 | |
| 382 | // test event setting | 336 | // test event setting |
| 383 | testing.expect(event.isSet() == false); | 337 | testing.expect(event.isSet() == false); |
| 384 | testing.expect(event.set(false) == false); | 338 | event.set(); |
| 385 | testing.expect(event.isSet() == true); | 339 | testing.expect(event.isSet() == true); |
| 386 | | 340 | |
| 387 | // test event resetting | 341 | // test event resetting |
| 388 | testing.expect(event.reset() == true); | 342 | event.reset(); |
| 389 | testing.expect(event.isSet() == false); | 343 | testing.expect(event.isSet() == false); |
| 390 | testing.expect(event.reset() == false); | | |
| 391 | | 344 | |
| 392 | // test cross thread signaling | 345 | // test event waiting (non-blocking) |
| 393 | const Context = struct { | 346 | event.set(); |
| 394 | event: ResetEvent, | 347 | event.wait(); |
| 395 | value: u128, | 348 | try event.timedWait(1); |
| 396 | | 349 | |
| 397 | fn receiver(self: *@This()) void { | 350 | // test cross-thread signaling |
| 398 | // wait for the sender to notify us with updated value | 351 | if (builtin.single_threaded) |
| 399 | assert(self.value == 0); | 352 | return; |
| 400 | assert((self.event.wait(1 * time.second) catch unreachable) == true); | | |
| 401 | assert(self.value == 1); | | |
| 402 | | 353 | |
| 403 | // wait for sender to sleep, then notify it of new value | 354 | const Context = struct { |
| 404 | time.sleep(50 * time.millisecond); | 355 | const Self = @This(); |
| 405 | self.value = 2; | 356 | |
| 406 | assert(self.event.set(false) == true); | 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 { | 369 | fn deinit(self: *Self) void { |
| 410 | // wait for the receiver() to start wait()'ing | 370 | self.in.deinit(); |
| 411 | time.sleep(50 * time.millisecond); | 371 | self.out.deinit(); |
| | 372 | self.* = undefined; |
| | 373 | } |
| 412 | | 374 | |
| 413 | // update value to 1 and notify the receiver() | 375 | fn sender(self: *Self) void { |
| 414 | assert(self.value == 0); | 376 | // update value and signal input |
| | 377 | testing.expect(self.value == 0); |
| 415 | self.value = 1; | 378 | self.value = 1; |
| 416 | assert(self.event.set(true) == true); | 379 | self.in.set(); |
| 417 | | 380 | |
| 418 | // wait for the receiver to update the value & notify us | 381 | // wait for receiver to update value and signal output |
| 419 | assert((try self.event.wait(1 * time.second)) == true); | 382 | self.out.wait(); |
| 420 | assert(self.value == 2); | 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(); | 390 | fn receiver(self: *Self) void { |
| 425 | var context = Context{ | 391 | // wait for sender to update value and signal input |
| 426 | .event = event, | 392 | self.in.wait(); |
| 427 | .value = 0, | 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 | defer receiver.wait(); | 409 | defer receiver.wait(); |
| 432 | try context.sender(); | 410 | context.sender(); |
| 433 | } | 411 | } |