| ... | @@ -36,7 +36,7 @@ pub const ResetEvent = struct { | ... | @@ -36,7 +36,7 @@ pub const ResetEvent = struct { |
| 36 | } | 36 | } |
| 37 | | 37 | |
| 38 | /// Sets the event if not already set and | 38 | /// Sets the event if not already set and |
| 39 | /// wakes up at least one thread waiting the event. | 39 | /// wakes up all the threads waiting on the event. |
| 40 | pub fn set(self: *ResetEvent) void { | 40 | pub fn set(self: *ResetEvent) void { |
| 41 | return self.os_event.set(); | 41 | return self.os_event.set(); |
| 42 | } | 42 | } |
| ... | @@ -135,7 +135,7 @@ const PosixEvent = struct { | ... | @@ -135,7 +135,7 @@ const PosixEvent = struct { |
| 135 | | 135 | |
| 136 | if (!self.is_set) { | 136 | if (!self.is_set) { |
| 137 | self.is_set = true; | 137 | self.is_set = true; |
| 138 | assert(c.pthread_cond_signal(&self.cond) == 0); | 138 | assert(c.pthread_cond_broadcast(&self.cond) == 0); |
| 139 | } | 139 | } |
| 140 | } | 140 | } |
| 141 | | 141 | |
| ... | @@ -181,40 +181,39 @@ const PosixEvent = struct { | ... | @@ -181,40 +181,39 @@ const PosixEvent = struct { |
| 181 | }; | 181 | }; |
| 182 | | 182 | |
| 183 | const AtomicEvent = struct { | 183 | const AtomicEvent = struct { |
| 184 | state: State, | 184 | waiters: u32, |
| 185 | | 185 | |
| 186 | const State = enum(i32) { | 186 | const WAKE = 1 << 0; |
| 187 | Empty, | 187 | const WAIT = 1 << 1; |
| 188 | Waiting, | | |
| 189 | Signaled, | | |
| 190 | }; | | |
| 191 | | 188 | |
| 192 | fn init() AtomicEvent { | 189 | fn init() AtomicEvent { |
| 193 | return AtomicEvent{ .state = .Empty }; | 190 | return AtomicEvent{ .waiters = 0 }; |
| 194 | } | 191 | } |
| 195 | | 192 | |
| 196 | fn deinit(self: *AtomicEvent) void { | 193 | fn deinit(self: *AtomicEvent) void { |
| 197 | self.* = undefined; | 194 | self.* = undefined; |
| 198 | } | 195 | } |
| 199 | | 196 | |
| 200 | fn isSet(self: *AtomicEvent) bool { | 197 | fn isSet(self: *const AtomicEvent) bool { |
| 201 | return @atomicLoad(State, &self.state, .Acquire) == .Signaled; | 198 | return @atomicLoad(u32, &self.waiters, .Acquire) == WAKE; |
| 202 | } | 199 | } |
| 203 | | 200 | |
| 204 | fn reset(self: *AtomicEvent) void { | 201 | fn reset(self: *AtomicEvent) void { |
| 205 | @atomicStore(State, &self.state, .Empty, .Monotonic); | 202 | @atomicStore(u32, &self.waiters, 0, .Monotonic); |
| 206 | } | 203 | } |
| 207 | | 204 | |
| 208 | fn set(self: *AtomicEvent) void { | 205 | fn set(self: *AtomicEvent) void { |
| 209 | if (@atomicRmw(State, &self.state, .Xchg, .Signaled, .Release) == .Waiting) | 206 | const waiters = @atomicRmw(u32, &self.waiters, .Xchg, WAKE, .Release); |
| 210 | Futex.wake(@ptrCast(*i32, &self.state)); | 207 | if (waiters >= WAIT) { |
| | 208 | return Futex.wake(&self.waiters, waiters >> 1); |
| | 209 | } |
| 211 | } | 210 | } |
| 212 | | 211 | |
| 213 | fn wait(self: *AtomicEvent, timeout: ?u64) !void { | 212 | fn wait(self: *AtomicEvent, timeout: ?u64) !void { |
| 214 | var state = @atomicLoad(State, &self.state, .Monotonic); | 213 | var waiters = @atomicLoad(u32, &self.waiters, .Acquire); |
| 215 | while (state == .Empty) { | 214 | while (waiters != WAKE) { |
| 216 | state = @cmpxchgWeak(State, &self.state, .Empty, .Waiting, .Acquire, .Monotonic) orelse | 215 | waiters = @cmpxchgWeak(u32, &self.waiters, waiters, waiters + WAIT, .Acquire, .Acquire) |
| 217 | return Futex.wait(@ptrCast(*i32, &self.state), @enumToInt(State.Waiting), timeout); | 216 | orelse return Futex.wait(&self.waiters, timeout); |
| 218 | } | 217 | } |
| 219 | } | 218 | } |
| 220 | | 219 | |
| ... | @@ -225,15 +224,15 @@ const AtomicEvent = struct { | ... | @@ -225,15 +224,15 @@ const AtomicEvent = struct { |
| 225 | }; | 224 | }; |
| 226 | | 225 | |
| 227 | const SpinFutex = struct { | 226 | const SpinFutex = struct { |
| 228 | fn wake(ptr: *i32) void {} | 227 | fn wake(waiters: *u32, wake_count: u32) void {} |
| 229 | | 228 | |
| 230 | fn wait(ptr: *i32, expected: i32, timeout: ?u64) !void { | 229 | fn wait(waiters: *u32, timeout: ?u64) !void { |
| 231 | // TODO: handle platforms where a monotonic timer isnt available | 230 | // TODO: handle platforms where a monotonic timer isnt available |
| 232 | var timer: time.Timer = undefined; | 231 | var timer: time.Timer = undefined; |
| 233 | if (timeout != null) | 232 | if (timeout != null) |
| 234 | timer = time.Timer.start() catch unreachable; | 233 | timer = time.Timer.start() catch unreachable; |
| 235 | | 234 | |
| 236 | while (@atomicLoad(i32, ptr, .Acquire) == expected) { | 235 | while (@atomicLoad(u32, waiters, .Acquire) != WAKE) { |
| 237 | SpinLock.yield(); | 236 | SpinLock.yield(); |
| 238 | if (timeout) |timeout_ns| { | 237 | if (timeout) |timeout_ns| { |
| 239 | if (timer.read() >= timeout_ns) | 238 | if (timer.read() >= timeout_ns) |
| ... | @@ -244,12 +243,14 @@ const AtomicEvent = struct { | ... | @@ -244,12 +243,14 @@ const AtomicEvent = struct { |
| 244 | }; | 243 | }; |
| 245 | | 244 | |
| 246 | const LinuxFutex = struct { | 245 | const LinuxFutex = struct { |
| 247 | fn wake(ptr: *i32) void { | 246 | fn wake(waiters: *u32, wake_count: u32) void { |
| 248 | const rc = linux.futex_wake(ptr, linux.FUTEX_WAKE | linux.FUTEX_PRIVATE_FLAG, 1); | 247 | const waiting = std.math.maxInt(i32); // wake_count |
| | 248 | const ptr = @ptrCast(*const i32, waiters); |
| | 249 | const rc = linux.futex_wake(ptr, linux.FUTEX_WAKE | linux.FUTEX_PRIVATE_FLAG, waiting); |
| 249 | assert(linux.getErrno(rc) == 0); | 250 | assert(linux.getErrno(rc) == 0); |
| 250 | } | 251 | } |
| 251 | | 252 | |
| 252 | fn wait(ptr: *i32, expected: i32, timeout: ?u64) !void { | 253 | fn wait(waiters: *u32, timeout: ?u64) !void { |
| 253 | var ts: linux.timespec = undefined; | 254 | var ts: linux.timespec = undefined; |
| 254 | var ts_ptr: ?*linux.timespec = null; | 255 | var ts_ptr: ?*linux.timespec = null; |
| 255 | if (timeout) |timeout_ns| { | 256 | if (timeout) |timeout_ns| { |
| ... | @@ -258,7 +259,12 @@ const AtomicEvent = struct { | ... | @@ -258,7 +259,12 @@ const AtomicEvent = struct { |
| 258 | ts.tv_nsec = @intCast(isize, timeout_ns % time.ns_per_s); | 259 | ts.tv_nsec = @intCast(isize, timeout_ns % time.ns_per_s); |
| 259 | } | 260 | } |
| 260 | | 261 | |
| 261 | while (@atomicLoad(i32, ptr, .Acquire) == expected) { | 262 | while (true) { |
| | 263 | const waiting = @atomicLoad(u32, waiters, .Acquire); |
| | 264 | if (waiting == WAKE) |
| | 265 | return; |
| | 266 | const expected = @intCast(i32, waiting); |
| | 267 | const ptr = @ptrCast(*const i32, waiters); |
| 262 | const rc = linux.futex_wait(ptr, linux.FUTEX_WAIT | linux.FUTEX_PRIVATE_FLAG, expected, ts_ptr); | 268 | const rc = linux.futex_wait(ptr, linux.FUTEX_WAIT | linux.FUTEX_PRIVATE_FLAG, expected, ts_ptr); |
| 263 | switch (linux.getErrno(rc)) { | 269 | switch (linux.getErrno(rc)) { |
| 264 | 0 => continue, | 270 | 0 => continue, |
| ... | @@ -272,15 +278,20 @@ const AtomicEvent = struct { | ... | @@ -272,15 +278,20 @@ const AtomicEvent = struct { |
| 272 | }; | 278 | }; |
| 273 | | 279 | |
| 274 | const WindowsFutex = struct { | 280 | const WindowsFutex = struct { |
| 275 | pub fn wake(ptr: *i32) void { | 281 | pub fn wake(waiters: *u32, wake_count: u32) void { |
| 276 | const handle = getEventHandle() orelse return SpinFutex.wake(ptr); | 282 | const handle = getEventHandle() orelse return SpinFutex.wake(waiters, wake_count); |
| 277 | const key = @ptrCast(*const c_void, ptr); | 283 | const key = @ptrCast(*const c_void, waiters); |
| 278 | const rc = windows.ntdll.NtReleaseKeyedEvent(handle, key, windows.FALSE, null); | 284 | |
| 279 | assert(rc == 0); | 285 | var waiting = wake_count; |
| | 286 | while (waiting != 0) : (waiting -= 1) { |
| | 287 | const rc = windows.ntdll.NtReleaseKeyedEvent(handle, key, windows.FALSE, null); |
| | 288 | assert(rc == 0); |
| | 289 | } |
| 280 | } | 290 | } |
| 281 | | 291 | |
| 282 | pub fn wait(ptr: *i32, expected: i32, timeout: ?u64) !void { | 292 | pub fn wait(waiters: *u32, timeout: ?u64) !void { |
| 283 | const handle = getEventHandle() orelse return SpinFutex.wait(ptr, expected, timeout); | 293 | const handle = getEventHandle() orelse return SpinFutex.wait(waiters, timeout); |
| | 294 | const key = @ptrCast(*const c_void, waiters); |
| 284 | | 295 | |
| 285 | // NT uses timeouts in units of 100ns with negative value being relative | 296 | // NT uses timeouts in units of 100ns with negative value being relative |
| 286 | var timeout_ptr: ?*windows.LARGE_INTEGER = null; | 297 | var timeout_ptr: ?*windows.LARGE_INTEGER = null; |
| ... | @@ -291,10 +302,26 @@ const AtomicEvent = struct { | ... | @@ -291,10 +302,26 @@ const AtomicEvent = struct { |
| 291 | } | 302 | } |
| 292 | | 303 | |
| 293 | // NtWaitForKeyedEvent doesnt have spurious wake-ups | 304 | // NtWaitForKeyedEvent doesnt have spurious wake-ups |
| 294 | const key = @ptrCast(*const c_void, ptr); | 305 | var rc = windows.ntdll.NtWaitForKeyedEvent(handle, key, windows.FALSE, timeout_ptr); |
| 295 | const rc = windows.ntdll.NtWaitForKeyedEvent(handle, key, windows.FALSE, timeout_ptr); | | |
| 296 | switch (rc) { | 306 | switch (rc) { |
| 297 | windows.WAIT_TIMEOUT => return error.TimedOut, | 307 | windows.WAIT_TIMEOUT => { |
| | 308 | // update the wait count to signal that we're not waiting anymore. |
| | 309 | // if the .set() thread already observed that we are, perform a |
| | 310 | // matching NtWaitForKeyedEvent so that the .set() thread doesn't |
| | 311 | // deadlock trying to run NtReleaseKeyedEvent above. |
| | 312 | var waiting = @atomicLoad(u32, waiters, .Monotonic); |
| | 313 | while (true) { |
| | 314 | if (waiting == WAKE) { |
| | 315 | rc = windows.ntdll.NtWaitForKeyedEvent(handle, key, windows.FALSE, null); |
| | 316 | assert(rc == windows.WAIT_OBJECT_0); |
| | 317 | break; |
| | 318 | } else { |
| | 319 | waiting = @cmpxchgWeak(u32, waiters, waiting, waiting - WAIT, .Acquire, .Monotonic) orelse break; |
| | 320 | continue; |
| | 321 | } |
| | 322 | } |
| | 323 | return error.TimedOut; |
| | 324 | }, |
| 298 | windows.WAIT_OBJECT_0 => {}, | 325 | windows.WAIT_OBJECT_0 => {}, |
| 299 | else => unreachable, | 326 | else => unreachable, |
| 300 | } | 327 | } |