| ... | ... | @@ -17,6 +17,10 @@ pub fn wait(cond: *Condition, mutex: *Mutex) void { |
| 17 | 17 | cond.impl.wait(mutex); |
| 18 | 18 | } |
| 19 | 19 | |
| 20 | pub fn timedWait(cond: *Condition, mutex: *Mutex, timeout_ns: u64) error{TimedOut}!void { |
| 21 | try cond.impl.timedWait(mutex, timeout_ns); |
| 22 | } |
| 23 | |
| 20 | 24 | pub fn signal(cond: *Condition) void { |
| 21 | 25 | cond.impl.signal(); |
| 22 | 26 | } |
| ... | ... | @@ -41,6 +45,14 @@ pub const SingleThreadedCondition = struct { |
| 41 | 45 | unreachable; // deadlock detected |
| 42 | 46 | } |
| 43 | 47 | |
| 48 | pub fn timedWait(cond: *SingleThreadedCondition, mutex: *Mutex, timeout_ns: u64) error{TimedOut}!void { |
| 49 | _ = cond; |
| 50 | _ = mutex; |
| 51 | _ = timeout_ns; |
| 52 | std.time.sleep(timeout_ns); |
| 53 | return error.TimedOut; |
| 54 | } |
| 55 | |
| 44 | 56 | pub fn signal(cond: *SingleThreadedCondition) void { |
| 45 | 57 | _ = cond; |
| 46 | 58 | } |
| ... | ... | @@ -63,6 +75,25 @@ pub const WindowsCondition = struct { |
| 63 | 75 | assert(rc != windows.FALSE); |
| 64 | 76 | } |
| 65 | 77 | |
| 78 | pub fn timedWait(cond: *WindowsCondition, mutex: *Mutex, timeout_ns: u64) error{TimedOut}!void { |
| 79 | var timeout_checked = std.math.cast(windows.DWORD, timeout_ns / std.time.ns_per_ms) catch overflow: { |
| 80 | break :overflow std.math.maxInt(windows.DWORD); |
| 81 | }; |
| 82 | |
| 83 | // Handle the case where timeout is INFINITE, otherwise SleepConditionVariableSRW's time-out never elapses |
| 84 | const timeout_overflowed = timeout_checked == windows.INFINITE; |
| 85 | timeout_checked -= @boolToInt(timeout_overflowed); |
| 86 | |
| 87 | const rc = windows.kernel32.SleepConditionVariableSRW( |
| 88 | &cond.cond, |
| 89 | &mutex.impl.srwlock, |
| 90 | timeout_checked, |
| 91 | @as(windows.ULONG, 0), |
| 92 | ); |
| 93 | if (rc == windows.FALSE and windows.kernel32.GetLastError() == windows.Win32Error.TIMEOUT) return error.TimedOut; |
| 94 | assert(rc != windows.FALSE); |
| 95 | } |
| 96 | |
| 66 | 97 | pub fn signal(cond: *WindowsCondition) void { |
| 67 | 98 | windows.kernel32.WakeConditionVariable(&cond.cond); |
| 68 | 99 | } |
| ... | ... | @@ -80,6 +111,24 @@ pub const PthreadCondition = struct { |
| 80 | 111 | assert(rc == .SUCCESS); |
| 81 | 112 | } |
| 82 | 113 | |
| 114 | pub fn timedWait(cond: *PthreadCondition, mutex: *Mutex, timeout_ns: u64) error{TimedOut}!void { |
| 115 | var ts: std.os.timespec = undefined; |
| 116 | std.os.clock_gettime(std.os.CLOCK.REALTIME, &ts) catch unreachable; |
| 117 | ts.tv_sec += @intCast(@TypeOf(ts.tv_sec), timeout_ns / std.time.ns_per_s); |
| 118 | ts.tv_nsec += @intCast(@TypeOf(ts.tv_nsec), timeout_ns % std.time.ns_per_s); |
| 119 | if (ts.tv_nsec >= std.time.ns_per_s) { |
| 120 | ts.tv_sec += 1; |
| 121 | ts.tv_nsec -= std.time.ns_per_s; |
| 122 | } |
| 123 | |
| 124 | const rc = std.c.pthread_cond_timedwait(&cond.cond, &mutex.impl.pthread_mutex, &ts); |
| 125 | return switch (rc) { |
| 126 | .SUCCESS => {}, |
| 127 | .TIMEDOUT => error.TimedOut, |
| 128 | else => unreachable, |
| 129 | }; |
| 130 | } |
| 131 | |
| 83 | 132 | pub fn signal(cond: *PthreadCondition) void { |
| 84 | 133 | const rc = std.c.pthread_cond_signal(&cond.cond); |
| 85 | 134 | assert(rc == .SUCCESS); |
| ... | ... | @@ -100,6 +149,7 @@ pub const AtomicCondition = struct { |
| 100 | 149 | |
| 101 | 150 | pub const QueueItem = struct { |
| 102 | 151 | futex: i32 = 0, |
| 152 | dequeued: bool = false, |
| 103 | 153 | |
| 104 | 154 | fn wait(cond: *@This()) void { |
| 105 | 155 | while (@atomicLoad(i32, &cond.futex, .Acquire) == 0) { |
| ... | ... | @@ -122,6 +172,39 @@ pub const AtomicCondition = struct { |
| 122 | 172 | } |
| 123 | 173 | } |
| 124 | 174 | |
| 175 | pub fn timedWait(cond: *@This(), timeout_ns: u64) error{TimedOut}!void { |
| 176 | const start_time = std.time.nanoTimestamp(); |
| 177 | while (@atomicLoad(i32, &cond.futex, .Acquire) == 0) { |
| 178 | switch (builtin.os.tag) { |
| 179 | .linux => { |
| 180 | var ts: std.os.timespec = undefined; |
| 181 | ts.tv_sec = @intCast(@TypeOf(ts.tv_sec), timeout_ns / std.time.ns_per_s); |
| 182 | ts.tv_nsec = @intCast(@TypeOf(ts.tv_nsec), timeout_ns % std.time.ns_per_s); |
| 183 | switch (linux.getErrno(linux.futex_wait( |
| 184 | &cond.futex, |
| 185 | linux.FUTEX.PRIVATE_FLAG | linux.FUTEX.WAIT, |
| 186 | 0, |
| 187 | &ts, |
| 188 | ))) { |
| 189 | .SUCCESS => {}, |
| 190 | .INTR => {}, |
| 191 | .AGAIN => {}, |
| 192 | .TIMEDOUT => return error.TimedOut, |
| 193 | .INVAL => {}, // possibly timeout overflow |
| 194 | .FAULT => unreachable, |
| 195 | else => unreachable, |
| 196 | } |
| 197 | }, |
| 198 | else => { |
| 199 | if (std.time.nanoTimestamp() - start_time >= timeout_ns) { |
| 200 | return error.TimedOut; |
| 201 | } |
| 202 | std.atomic.spinLoopHint(); |
| 203 | }, |
| 204 | } |
| 205 | } |
| 206 | } |
| 207 | |
| 125 | 208 | fn notify(cond: *@This()) void { |
| 126 | 209 | @atomicStore(i32, &cond.futex, 1, .Release); |
| 127 | 210 | |
| ... | ... | @@ -158,6 +241,41 @@ pub const AtomicCondition = struct { |
| 158 | 241 | mutex.lock(); |
| 159 | 242 | } |
| 160 | 243 | |
| 244 | pub fn timedWait(cond: *AtomicCondition, mutex: *Mutex, timeout_ns: u64) error{TimedOut}!void { |
| 245 | var waiter = QueueList.Node{ .data = .{} }; |
| 246 | |
| 247 | { |
| 248 | cond.queue_mutex.lock(); |
| 249 | defer cond.queue_mutex.unlock(); |
| 250 | |
| 251 | cond.queue_list.prepend(&waiter); |
| 252 | @atomicStore(bool, &cond.pending, true, .SeqCst); |
| 253 | } |
| 254 | |
| 255 | var timed_out = false; |
| 256 | mutex.unlock(); |
| 257 | defer mutex.lock(); |
| 258 | waiter.data.timedWait(timeout_ns) catch |err| switch (err) { |
| 259 | error.TimedOut => { |
| 260 | defer if (!timed_out) { |
| 261 | waiter.data.wait(); |
| 262 | }; |
| 263 | cond.queue_mutex.lock(); |
| 264 | defer cond.queue_mutex.unlock(); |
| 265 | |
| 266 | if (!waiter.data.dequeued) { |
| 267 | timed_out = true; |
| 268 | cond.queue_list.remove(&waiter); |
| 269 | } |
| 270 | }, |
| 271 | else => unreachable, |
| 272 | }; |
| 273 | |
| 274 | if (timed_out) { |
| 275 | return error.TimedOut; |
| 276 | } |
| 277 | } |
| 278 | |
| 161 | 279 | pub fn signal(cond: *AtomicCondition) void { |
| 162 | 280 | if (@atomicLoad(bool, &cond.pending, .SeqCst) == false) |
| 163 | 281 | return; |
| ... | ... | @@ -167,12 +285,16 @@ pub const AtomicCondition = struct { |
| 167 | 285 | defer cond.queue_mutex.unlock(); |
| 168 | 286 | |
| 169 | 287 | const maybe_waiter = cond.queue_list.popFirst(); |
| 288 | if (maybe_waiter) |waiter| { |
| 289 | waiter.data.dequeued = true; |
| 290 | } |
| 170 | 291 | @atomicStore(bool, &cond.pending, cond.queue_list.first != null, .SeqCst); |
| 171 | 292 | break :blk maybe_waiter; |
| 172 | 293 | }; |
| 173 | 294 | |
| 174 | | if (maybe_waiter) |waiter| |
| 295 | if (maybe_waiter) |waiter| { |
| 175 | 296 | waiter.data.notify(); |
| 297 | } |
| 176 | 298 | } |
| 177 | 299 | |
| 178 | 300 | pub fn broadcast(cond: *AtomicCondition) void { |
| ... | ... | @@ -186,12 +308,19 @@ pub const AtomicCondition = struct { |
| 186 | 308 | defer cond.queue_mutex.unlock(); |
| 187 | 309 | |
| 188 | 310 | const waiters = cond.queue_list; |
| 311 | |
| 312 | var it = waiters.first; |
| 313 | while (it) |node| : (it = node.next) { |
| 314 | node.data.dequeued = true; |
| 315 | } |
| 316 | |
| 189 | 317 | cond.queue_list = .{}; |
| 190 | 318 | break :blk waiters; |
| 191 | 319 | }; |
| 192 | 320 | |
| 193 | | while (waiters.popFirst()) |waiter| |
| 321 | while (waiters.popFirst()) |waiter| { |
| 194 | 322 | waiter.data.notify(); |
| 323 | } |
| 195 | 324 | } |
| 196 | 325 | }; |
| 197 | 326 | |
| ... | ... | @@ -238,3 +367,45 @@ test "Thread.Condition" { |
| 238 | 367 | |
| 239 | 368 | for (threads) |t| t.join(); |
| 240 | 369 | } |
| 370 | |
| 371 | test "Thread.Condition.timedWait" { |
| 372 | if (builtin.single_threaded) { |
| 373 | return error.SkipZigTest; |
| 374 | } |
| 375 | |
| 376 | var cond = Condition{}; |
| 377 | var mut = Mutex{}; |
| 378 | |
| 379 | // Expect a timeout, as the condition variable is never signaled |
| 380 | { |
| 381 | mut.lock(); |
| 382 | defer mut.unlock(); |
| 383 | try testing.expectError(error.TimedOut, cond.timedWait(&mut, 10 * std.time.ns_per_ms)); |
| 384 | } |
| 385 | |
| 386 | // Expect a signal before timeout |
| 387 | { |
| 388 | const TestContext = struct { |
| 389 | cond: *Condition, |
| 390 | mutex: *Mutex, |
| 391 | n: *u32, |
| 392 | fn worker(ctx: *@This()) void { |
| 393 | ctx.mutex.lock(); |
| 394 | defer ctx.mutex.unlock(); |
| 395 | ctx.n.* = 1; |
| 396 | ctx.cond.signal(); |
| 397 | } |
| 398 | }; |
| 399 | |
| 400 | var n: u32 = 0; |
| 401 | |
| 402 | var ctx = TestContext{ .cond = &cond, .mutex = &mut, .n = &n }; |
| 403 | mut.lock(); |
| 404 | var thread = try std.Thread.spawn(.{}, TestContext.worker, .{&ctx}); |
| 405 | // Looped check to handle spurious wakeups |
| 406 | while (n != 1) try cond.timedWait(&mut, 500 * std.time.ns_per_ms); |
| 407 | mut.unlock(); |
| 408 | try testing.expect(n == 1); |
| 409 | thread.join(); |
| 410 | } |
| 411 | } |