| ... | @@ -17,6 +17,10 @@ pub fn wait(cond: *Condition, mutex: *Mutex) void { | ... | @@ -17,6 +17,10 @@ pub fn wait(cond: *Condition, mutex: *Mutex) void { |
| 17 | cond.impl.wait(mutex); | 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 | pub fn signal(cond: *Condition) void { | 24 | pub fn signal(cond: *Condition) void { |
| 21 | cond.impl.signal(); | 25 | cond.impl.signal(); |
| 22 | } | 26 | } |
| ... | @@ -41,6 +45,14 @@ pub const SingleThreadedCondition = struct { | ... | @@ -41,6 +45,14 @@ pub const SingleThreadedCondition = struct { |
| 41 | unreachable; // deadlock detected | 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 | pub fn signal(cond: *SingleThreadedCondition) void { | 56 | pub fn signal(cond: *SingleThreadedCondition) void { |
| 45 | _ = cond; | 57 | _ = cond; |
| 46 | } | 58 | } |
| ... | @@ -63,6 +75,25 @@ pub const WindowsCondition = struct { | ... | @@ -63,6 +75,25 @@ pub const WindowsCondition = struct { |
| 63 | assert(rc != windows.FALSE); | 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 | pub fn signal(cond: *WindowsCondition) void { | 97 | pub fn signal(cond: *WindowsCondition) void { |
| 67 | windows.kernel32.WakeConditionVariable(&cond.cond); | 98 | windows.kernel32.WakeConditionVariable(&cond.cond); |
| 68 | } | 99 | } |
| ... | @@ -80,6 +111,24 @@ pub const PthreadCondition = struct { | ... | @@ -80,6 +111,24 @@ pub const PthreadCondition = struct { |
| 80 | assert(rc == .SUCCESS); | 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 | pub fn signal(cond: *PthreadCondition) void { | 132 | pub fn signal(cond: *PthreadCondition) void { |
| 84 | const rc = std.c.pthread_cond_signal(&cond.cond); | 133 | const rc = std.c.pthread_cond_signal(&cond.cond); |
| 85 | assert(rc == .SUCCESS); | 134 | assert(rc == .SUCCESS); |
| ... | @@ -100,6 +149,7 @@ pub const AtomicCondition = struct { | ... | @@ -100,6 +149,7 @@ pub const AtomicCondition = struct { |
| 100 | | 149 | |
| 101 | pub const QueueItem = struct { | 150 | pub const QueueItem = struct { |
| 102 | futex: i32 = 0, | 151 | futex: i32 = 0, |
| | 152 | dequeued: bool = false, |
| 103 | | 153 | |
| 104 | fn wait(cond: *@This()) void { | 154 | fn wait(cond: *@This()) void { |
| 105 | while (@atomicLoad(i32, &cond.futex, .Acquire) == 0) { | 155 | while (@atomicLoad(i32, &cond.futex, .Acquire) == 0) { |
| ... | @@ -122,6 +172,39 @@ pub const AtomicCondition = struct { | ... | @@ -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 | fn notify(cond: *@This()) void { | 208 | fn notify(cond: *@This()) void { |
| 126 | @atomicStore(i32, &cond.futex, 1, .Release); | 209 | @atomicStore(i32, &cond.futex, 1, .Release); |
| 127 | | 210 | |
| ... | @@ -158,6 +241,41 @@ pub const AtomicCondition = struct { | ... | @@ -158,6 +241,41 @@ pub const AtomicCondition = struct { |
| 158 | mutex.lock(); | 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 | pub fn signal(cond: *AtomicCondition) void { | 279 | pub fn signal(cond: *AtomicCondition) void { |
| 162 | if (@atomicLoad(bool, &cond.pending, .SeqCst) == false) | 280 | if (@atomicLoad(bool, &cond.pending, .SeqCst) == false) |
| 163 | return; | 281 | return; |
| ... | @@ -167,12 +285,16 @@ pub const AtomicCondition = struct { | ... | @@ -167,12 +285,16 @@ pub const AtomicCondition = struct { |
| 167 | defer cond.queue_mutex.unlock(); | 285 | defer cond.queue_mutex.unlock(); |
| 168 | | 286 | |
| 169 | const maybe_waiter = cond.queue_list.popFirst(); | 287 | const maybe_waiter = cond.queue_list.popFirst(); |
| | 288 | if (maybe_waiter) |waiter| { |
| | 289 | waiter.data.dequeued = true; |
| | 290 | } |
| 170 | @atomicStore(bool, &cond.pending, cond.queue_list.first != null, .SeqCst); | 291 | @atomicStore(bool, &cond.pending, cond.queue_list.first != null, .SeqCst); |
| 171 | break :blk maybe_waiter; | 292 | break :blk maybe_waiter; |
| 172 | }; | 293 | }; |
| 173 | | 294 | |
| 174 | if (maybe_waiter) |waiter| | 295 | if (maybe_waiter) |waiter| { |
| 175 | waiter.data.notify(); | 296 | waiter.data.notify(); |
| | 297 | } |
| 176 | } | 298 | } |
| 177 | | 299 | |
| 178 | pub fn broadcast(cond: *AtomicCondition) void { | 300 | pub fn broadcast(cond: *AtomicCondition) void { |
| ... | @@ -186,12 +308,19 @@ pub const AtomicCondition = struct { | ... | @@ -186,12 +308,19 @@ pub const AtomicCondition = struct { |
| 186 | defer cond.queue_mutex.unlock(); | 308 | defer cond.queue_mutex.unlock(); |
| 187 | | 309 | |
| 188 | const waiters = cond.queue_list; | 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 | cond.queue_list = .{}; | 317 | cond.queue_list = .{}; |
| 190 | break :blk waiters; | 318 | break :blk waiters; |
| 191 | }; | 319 | }; |
| 192 | | 320 | |
| 193 | while (waiters.popFirst()) |waiter| | 321 | while (waiters.popFirst()) |waiter| { |
| 194 | waiter.data.notify(); | 322 | waiter.data.notify(); |
| | 323 | } |
| 195 | } | 324 | } |
| 196 | }; | 325 | }; |
| 197 | | 326 | |
| ... | @@ -238,3 +367,45 @@ test "Thread.Condition" { | ... | @@ -238,3 +367,45 @@ test "Thread.Condition" { |
| 238 | | 367 | |
| 239 | for (threads) |t| t.join(); | 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 | } |