| ... | ... | @@ -155,6 +155,220 @@ const Thread = struct { |
| 155 | 155 | fn currentSignalId() SignaleeId { |
| 156 | 156 | return if (std.Thread.use_pthreads) std.c.pthread_self() else std.Thread.getCurrentId(); |
| 157 | 157 | } |
| 158 | |
| 159 | fn futexWaitUncancelable(ptr: *const u32, expect: u32) void { |
| 160 | return Thread.futexWaitTimed(null, ptr, expect, null) catch unreachable; |
| 161 | } |
| 162 | |
| 163 | fn futexWait(thread: *Thread, ptr: *const u32, expect: u32) Io.Cancelable!void { |
| 164 | return Thread.futexWaitTimed(thread, ptr, expect, null) catch |err| switch (err) { |
| 165 | error.Canceled => return error.Canceled, |
| 166 | error.Timeout => unreachable, |
| 167 | }; |
| 168 | } |
| 169 | |
| 170 | fn futexWaitTimed(thread: ?*Thread, ptr: *const u32, expect: u32, timeout_ns: ?u64) Io.Cancelable!void { |
| 171 | @branchHint(.cold); |
| 172 | |
| 173 | if (builtin.single_threaded) unreachable; // nobody would ever wake us |
| 174 | |
| 175 | if (builtin.cpu.arch.isWasm()) { |
| 176 | comptime assert(builtin.cpu.has(.wasm, .atomics)); |
| 177 | if (thread) |t| try t.checkCancel(); |
| 178 | const to: i64 = if (timeout_ns) |ns| ns else -1; |
| 179 | const signed_expect: i32 = @bitCast(expect); |
| 180 | const result = asm volatile ( |
| 181 | \\local.get %[ptr] |
| 182 | \\local.get %[expected] |
| 183 | \\local.get %[timeout] |
| 184 | \\memory.atomic.wait32 0 |
| 185 | \\local.set %[ret] |
| 186 | : [ret] "=r" (-> u32), |
| 187 | : [ptr] "r" (ptr), |
| 188 | [expected] "r" (signed_expect), |
| 189 | [timeout] "r" (to), |
| 190 | ); |
| 191 | switch (result) { |
| 192 | 0 => {}, // ok |
| 193 | 1 => {}, // expected != loaded |
| 194 | 2 => {}, // timeout |
| 195 | else => assert(!is_debug), |
| 196 | } |
| 197 | } else switch (native_os) { |
| 198 | .linux => { |
| 199 | const linux = std.os.linux; |
| 200 | var ts_buffer: linux.timespec = undefined; |
| 201 | const ts: ?*linux.timespec = if (timeout_ns) |ns| ts: { |
| 202 | ts_buffer = timestampToPosix(ns); |
| 203 | break :ts &ts_buffer; |
| 204 | } else null; |
| 205 | if (thread) |t| try t.beginSyscall(); |
| 206 | const rc = linux.futex_4arg(ptr, .{ .cmd = .WAIT, .private = true }, expect, ts); |
| 207 | if (thread) |t| t.endSyscall(); |
| 208 | switch (linux.errno(rc)) { |
| 209 | .SUCCESS => {}, // notified by `wake()` |
| 210 | .INTR => {}, // caller's responsibility to retry |
| 211 | .AGAIN => {}, // ptr.* != expect |
| 212 | .INVAL => {}, // possibly timeout overflow |
| 213 | .TIMEDOUT => {}, // timeout |
| 214 | .FAULT => recoverableOsBugDetected(), // ptr was invalid |
| 215 | else => recoverableOsBugDetected(), |
| 216 | } |
| 217 | }, |
| 218 | .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { |
| 219 | const c = std.c; |
| 220 | const flags: c.UL = .{ |
| 221 | .op = .COMPARE_AND_WAIT, |
| 222 | .NO_ERRNO = true, |
| 223 | }; |
| 224 | if (thread) |t| try t.beginSyscall(); |
| 225 | const status = switch (darwin_supports_ulock_wait2) { |
| 226 | true => c.__ulock_wait2(flags, ptr, expect, ns: { |
| 227 | const ns = timeout_ns orelse break :ns 0; |
| 228 | if (ns == 0) break :ns 1; |
| 229 | break :ns ns; |
| 230 | }, 0), |
| 231 | false => c.__ulock_wait(flags, ptr, expect, us: { |
| 232 | const ns = timeout_ns orelse break :us 0; |
| 233 | const us = std.math.lossyCast(u32, ns / std.time.ns_per_us); |
| 234 | if (us == 0) break :us 1; |
| 235 | break :us us; |
| 236 | }), |
| 237 | }; |
| 238 | if (thread) |t| t.endSyscall(); |
| 239 | if (status >= 0) return; |
| 240 | switch (@as(c.E, @enumFromInt(-status))) { |
| 241 | .INTR => {}, // spurious wake |
| 242 | // Address of the futex was paged out. This is unlikely, but possible in theory, and |
| 243 | // pthread/libdispatch on darwin bother to handle it. In this case we'll return |
| 244 | // without waiting, but the caller should retry anyway. |
| 245 | .FAULT => {}, |
| 246 | .TIMEDOUT => {}, // timeout |
| 247 | else => recoverableOsBugDetected(), |
| 248 | } |
| 249 | }, |
| 250 | .windows => { |
| 251 | var timeout_value: windows.LARGE_INTEGER = undefined; |
| 252 | var timeout_ptr: ?*const windows.LARGE_INTEGER = null; |
| 253 | // NTDLL functions work with time in units of 100 nanoseconds. |
| 254 | // Positive values are absolute deadlines while negative values are relative durations. |
| 255 | if (timeout_ns) |delay| { |
| 256 | timeout_value = @as(windows.LARGE_INTEGER, @intCast(delay / 100)); |
| 257 | timeout_value = -timeout_value; |
| 258 | timeout_ptr = &timeout_value; |
| 259 | } |
| 260 | if (thread) |t| try t.checkCancel(); |
| 261 | switch (windows.ntdll.RtlWaitOnAddress(ptr, &expect, @sizeOf(@TypeOf(expect)), timeout_ptr)) { |
| 262 | .SUCCESS => {}, |
| 263 | .CANCELLED => {}, |
| 264 | .TIMEOUT => {}, // timeout |
| 265 | else => recoverableOsBugDetected(), |
| 266 | } |
| 267 | }, |
| 268 | .freebsd => { |
| 269 | const flags = @intFromEnum(std.c.UMTX_OP.WAIT_UINT_PRIVATE); |
| 270 | var tm_size: usize = 0; |
| 271 | var tm: std.c._umtx_time = undefined; |
| 272 | var tm_ptr: ?*const std.c._umtx_time = null; |
| 273 | if (timeout_ns) |ns| { |
| 274 | tm_ptr = &tm; |
| 275 | tm_size = @sizeOf(@TypeOf(tm)); |
| 276 | tm.flags = 0; // use relative time not UMTX_ABSTIME |
| 277 | tm.clockid = .MONOTONIC; |
| 278 | tm.timeout = timestampToPosix(ns); |
| 279 | } |
| 280 | if (thread) |t| try t.beginSyscall(); |
| 281 | const rc = std.c._umtx_op(@intFromPtr(ptr), flags, @as(c_ulong, expect), tm_size, @intFromPtr(tm_ptr)); |
| 282 | if (thread) |t| t.endSyscall(); |
| 283 | if (is_debug) switch (posix.errno(rc)) { |
| 284 | .SUCCESS => {}, |
| 285 | .FAULT => unreachable, // one of the args points to invalid memory |
| 286 | .INVAL => unreachable, // arguments should be correct |
| 287 | .TIMEDOUT => {}, // timeout |
| 288 | .INTR => {}, // spurious wake |
| 289 | else => unreachable, |
| 290 | }; |
| 291 | }, |
| 292 | else => @compileError("unimplemented: futexWait"), |
| 293 | } |
| 294 | } |
| 295 | |
| 296 | fn futexWake(ptr: *const u32, max_waiters: u32) void { |
| 297 | @branchHint(.cold); |
| 298 | |
| 299 | if (builtin.single_threaded) return; // nothing to wake up |
| 300 | |
| 301 | if (builtin.cpu.arch.isWasm()) { |
| 302 | comptime assert(builtin.cpu.has(.wasm, .atomics)); |
| 303 | assert(max_waiters != 0); |
| 304 | const woken_count = asm volatile ( |
| 305 | \\local.get %[ptr] |
| 306 | \\local.get %[waiters] |
| 307 | \\memory.atomic.notify 0 |
| 308 | \\local.set %[ret] |
| 309 | : [ret] "=r" (-> u32), |
| 310 | : [ptr] "r" (ptr), |
| 311 | [waiters] "r" (max_waiters), |
| 312 | ); |
| 313 | _ = woken_count; // can be 0 when linker flag 'shared-memory' is not enabled |
| 314 | } else switch (native_os) { |
| 315 | .linux => { |
| 316 | const linux = std.os.linux; |
| 317 | switch (linux.errno(linux.futex_3arg( |
| 318 | ptr, |
| 319 | .{ .cmd = .WAKE, .private = true }, |
| 320 | @min(max_waiters, std.math.maxInt(i32)), |
| 321 | ))) { |
| 322 | .SUCCESS => return, // successful wake up |
| 323 | .INVAL => return, // invalid futex_wait() on ptr done elsewhere |
| 324 | .FAULT => return, // pointer became invalid while doing the wake |
| 325 | else => return recoverableOsBugDetected(), // deadlock due to operating system bug |
| 326 | } |
| 327 | }, |
| 328 | .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { |
| 329 | const c = std.c; |
| 330 | const flags: c.UL = .{ |
| 331 | .op = .COMPARE_AND_WAIT, |
| 332 | .NO_ERRNO = true, |
| 333 | .WAKE_ALL = max_waiters > 1, |
| 334 | }; |
| 335 | while (true) { |
| 336 | const status = c.__ulock_wake(flags, ptr, 0); |
| 337 | if (status >= 0) return; |
| 338 | switch (@as(c.E, @enumFromInt(-status))) { |
| 339 | .INTR, .CANCELED => continue, // spurious wake() |
| 340 | .FAULT => unreachable, // __ulock_wake doesn't generate EFAULT according to darwin pthread_cond_t |
| 341 | .NOENT => return, // nothing was woken up |
| 342 | .ALREADY => unreachable, // only for UL.Op.WAKE_THREAD |
| 343 | else => unreachable, // deadlock due to operating system bug |
| 344 | } |
| 345 | } |
| 346 | }, |
| 347 | .windows => { |
| 348 | assert(max_waiters != 0); |
| 349 | switch (max_waiters) { |
| 350 | 1 => windows.ntdll.RtlWakeAddressSingle(ptr), |
| 351 | else => windows.ntdll.RtlWakeAddressAll(ptr), |
| 352 | } |
| 353 | }, |
| 354 | .freebsd => { |
| 355 | const rc = std.c._umtx_op( |
| 356 | @intFromPtr(ptr), |
| 357 | @intFromEnum(std.c.UMTX_OP.WAKE_PRIVATE), |
| 358 | @as(c_ulong, max_waiters), |
| 359 | 0, // there is no timeout struct |
| 360 | 0, // there is no timeout struct pointer |
| 361 | ); |
| 362 | switch (posix.errno(rc)) { |
| 363 | .SUCCESS => {}, |
| 364 | .FAULT => {}, // it's ok if the ptr doesn't point to valid memory |
| 365 | .INVAL => unreachable, // arguments should be correct |
| 366 | else => unreachable, // deadlock due to operating system bug |
| 367 | } |
| 368 | }, |
| 369 | else => @compileError("unimplemented: futexWake"), |
| 370 | } |
| 371 | } |
| 158 | 372 | }; |
| 159 | 373 | |
| 160 | 374 | const max_iovecs_len = 8; |
| ... | ... | @@ -403,6 +617,10 @@ pub fn io(t: *Threaded) Io { |
| 403 | 617 | .groupWait = groupWait, |
| 404 | 618 | .groupCancel = groupCancel, |
| 405 | 619 | |
| 620 | .futexWait = futexWait, |
| 621 | .futexWaitUncancelable = futexWaitUncancelable, |
| 622 | .futexWake = futexWake, |
| 623 | |
| 406 | 624 | .mutexLock = mutexLock, |
| 407 | 625 | .mutexLockUncancelable = mutexLockUncancelable, |
| 408 | 626 | .mutexUnlock = mutexUnlock, |
| ... | ... | @@ -499,6 +717,10 @@ pub fn ioBasic(t: *Threaded) Io { |
| 499 | 717 | .groupWait = groupWait, |
| 500 | 718 | .groupCancel = groupCancel, |
| 501 | 719 | |
| 720 | .futexWait = futexWait, |
| 721 | .futexWaitUncancelable = futexWaitUncancelable, |
| 722 | .futexWake = futexWake, |
| 723 | |
| 502 | 724 | .mutexLock = mutexLock, |
| 503 | 725 | .mutexLockUncancelable = mutexLockUncancelable, |
| 504 | 726 | .mutexUnlock = mutexUnlock, |
| ... | ... | @@ -1020,6 +1242,38 @@ fn cancel( |
| 1020 | 1242 | ac.waitAndDeinit(t, result); |
| 1021 | 1243 | } |
| 1022 | 1244 | |
| 1245 | fn futexWait(userdata: ?*anyopaque, ptr: *const u32, expected: u32, timeout: Io.Timeout) Io.Cancelable!void { |
| 1246 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 1247 | const current_thread = Thread.getCurrent(t); |
| 1248 | const t_io = ioBasic(t); |
| 1249 | const timeout_ns: ?u64 = ns: { |
| 1250 | const d = (timeout.toDurationFromNow(t_io) catch break :ns 10) orelse break :ns null; |
| 1251 | break :ns std.math.lossyCast(u64, d.raw.toNanoseconds()); |
| 1252 | }; |
| 1253 | switch (native_os) { |
| 1254 | .illumos, .netbsd, .openbsd => @panic("TODO"), |
| 1255 | else => try current_thread.futexWaitTimed(ptr, expected, timeout_ns), |
| 1256 | } |
| 1257 | } |
| 1258 | |
| 1259 | fn futexWaitUncancelable(userdata: ?*anyopaque, ptr: *const u32, expected: u32) void { |
| 1260 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 1261 | _ = t; |
| 1262 | switch (native_os) { |
| 1263 | .illumos, .netbsd, .openbsd => @panic("TODO"), |
| 1264 | else => Thread.futexWaitUncancelable(ptr, expected), |
| 1265 | } |
| 1266 | } |
| 1267 | |
| 1268 | fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { |
| 1269 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 1270 | _ = t; |
| 1271 | switch (native_os) { |
| 1272 | .illumos, .netbsd, .openbsd => @panic("TODO"), |
| 1273 | else => Thread.futexWake(ptr, max_waiters), |
| 1274 | } |
| 1275 | } |
| 1276 | |
| 1023 | 1277 | fn mutexLock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) Io.Cancelable!void { |
| 1024 | 1278 | if (builtin.single_threaded) unreachable; // Interface should have prevented this. |
| 1025 | 1279 | if (native_os == .netbsd) @panic("TODO"); |
| ... | ... | @@ -1027,10 +1281,10 @@ fn mutexLock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex |
| 1027 | 1281 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 1028 | 1282 | const current_thread = Thread.getCurrent(t); |
| 1029 | 1283 | if (prev_state == .contended) { |
| 1030 | | try futexWait(current_thread, @ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); |
| 1284 | try current_thread.futexWait(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); |
| 1031 | 1285 | } |
| 1032 | 1286 | while (@atomicRmw(Io.Mutex.State, &mutex.state, .Xchg, .contended, .acquire) != .unlocked) { |
| 1033 | | try futexWait(current_thread, @ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); |
| 1287 | try current_thread.futexWait(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); |
| 1034 | 1288 | } |
| 1035 | 1289 | } |
| 1036 | 1290 | |
| ... | ... | @@ -1040,10 +1294,10 @@ fn mutexLockUncancelable(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mute |
| 1040 | 1294 | if (native_os == .openbsd) @panic("TODO"); |
| 1041 | 1295 | _ = userdata; |
| 1042 | 1296 | if (prev_state == .contended) { |
| 1043 | | futexWaitUncancelable(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); |
| 1297 | Thread.futexWaitUncancelable(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); |
| 1044 | 1298 | } |
| 1045 | 1299 | while (@atomicRmw(Io.Mutex.State, &mutex.state, .Xchg, .contended, .acquire) != .unlocked) { |
| 1046 | | futexWaitUncancelable(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); |
| 1300 | Thread.futexWaitUncancelable(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); |
| 1047 | 1301 | } |
| 1048 | 1302 | } |
| 1049 | 1303 | |
| ... | ... | @@ -1054,7 +1308,7 @@ fn mutexUnlock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mut |
| 1054 | 1308 | _ = userdata; |
| 1055 | 1309 | _ = prev_state; |
| 1056 | 1310 | if (@atomicRmw(Io.Mutex.State, &mutex.state, .Xchg, .unlocked, .release) == .contended) { |
| 1057 | | futexWake(@ptrCast(&mutex.state), 1); |
| 1311 | Thread.futexWake(@ptrCast(&mutex.state), 1); |
| 1058 | 1312 | } |
| 1059 | 1313 | } |
| 1060 | 1314 | |
| ... | ... | @@ -1081,7 +1335,7 @@ fn conditionWaitUncancelable(userdata: ?*anyopaque, cond: *Io.Condition, mutex: |
| 1081 | 1335 | defer mutex.lockUncancelable(t_io); |
| 1082 | 1336 | |
| 1083 | 1337 | while (true) { |
| 1084 | | futexWaitUncancelable(cond_epoch, epoch); |
| 1338 | Thread.futexWaitUncancelable(@ptrCast(cond_epoch), epoch); |
| 1085 | 1339 | epoch = cond_epoch.load(.acquire); |
| 1086 | 1340 | state = cond_state.load(.monotonic); |
| 1087 | 1341 | while (state & signal_mask != 0) { |
| ... | ... | @@ -1125,7 +1379,7 @@ fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) I |
| 1125 | 1379 | defer mutex.lockUncancelable(t_io); |
| 1126 | 1380 | |
| 1127 | 1381 | while (true) { |
| 1128 | | try futexWait(current_thread, cond_epoch, epoch); |
| 1382 | try current_thread.futexWait(@ptrCast(cond_epoch), epoch); |
| 1129 | 1383 | |
| 1130 | 1384 | epoch = cond_epoch.load(.acquire); |
| 1131 | 1385 | state = cond_state.load(.monotonic); |
| ... | ... | @@ -1198,7 +1452,7 @@ fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition. |
| 1198 | 1452 | _ = cond_epoch.fetchAdd(1, .release); |
| 1199 | 1453 | if (native_os == .netbsd) @panic("TODO"); |
| 1200 | 1454 | if (native_os == .openbsd) @panic("TODO"); |
| 1201 | | futexWake(cond_epoch, to_wake); |
| 1455 | Thread.futexWake(@ptrCast(cond_epoch), to_wake); |
| 1202 | 1456 | return; |
| 1203 | 1457 | }; |
| 1204 | 1458 | } |
| ... | ... | @@ -6612,257 +6866,6 @@ fn copyCanon(canonical_name_buffer: *[HostName.max_len]u8, name: []const u8) Hos |
| 6612 | 6866 | /// ulock_wait2() uses 64-bit nano-second timeouts (with the same convention) |
| 6613 | 6867 | const darwin_supports_ulock_wait2 = builtin.os.version_range.semver.min.major >= 11; |
| 6614 | 6868 | |
| 6615 | | fn futexWait(current_thread: *Thread, ptr: *const std.atomic.Value(u32), expect: u32) Io.Cancelable!void { |
| 6616 | | @branchHint(.cold); |
| 6617 | | |
| 6618 | | if (builtin.cpu.arch.isWasm()) { |
| 6619 | | comptime assert(builtin.cpu.has(.wasm, .atomics)); |
| 6620 | | try current_thread.checkCancel(); |
| 6621 | | const timeout: i64 = -1; |
| 6622 | | const signed_expect: i32 = @bitCast(expect); |
| 6623 | | const result = asm volatile ( |
| 6624 | | \\local.get %[ptr] |
| 6625 | | \\local.get %[expected] |
| 6626 | | \\local.get %[timeout] |
| 6627 | | \\memory.atomic.wait32 0 |
| 6628 | | \\local.set %[ret] |
| 6629 | | : [ret] "=r" (-> u32), |
| 6630 | | : [ptr] "r" (&ptr.raw), |
| 6631 | | [expected] "r" (signed_expect), |
| 6632 | | [timeout] "r" (timeout), |
| 6633 | | ); |
| 6634 | | switch (result) { |
| 6635 | | 0 => {}, // ok |
| 6636 | | 1 => {}, // expected != loaded |
| 6637 | | 2 => assert(!is_debug), // timeout |
| 6638 | | else => assert(!is_debug), |
| 6639 | | } |
| 6640 | | } else switch (native_os) { |
| 6641 | | .linux => { |
| 6642 | | const linux = std.os.linux; |
| 6643 | | try current_thread.beginSyscall(); |
| 6644 | | const rc = linux.futex_4arg(ptr, .{ .cmd = .WAIT, .private = true }, expect, null); |
| 6645 | | current_thread.endSyscall(); |
| 6646 | | switch (linux.errno(rc)) { |
| 6647 | | .SUCCESS => {}, // notified by `wake()` |
| 6648 | | .INTR => {}, // caller's responsibility to retry |
| 6649 | | .AGAIN => {}, // ptr.* != expect |
| 6650 | | .INVAL => {}, // possibly timeout overflow |
| 6651 | | .TIMEDOUT => recoverableOsBugDetected(), |
| 6652 | | .FAULT => recoverableOsBugDetected(), // ptr was invalid |
| 6653 | | else => recoverableOsBugDetected(), |
| 6654 | | } |
| 6655 | | }, |
| 6656 | | .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { |
| 6657 | | const c = std.c; |
| 6658 | | const flags: c.UL = .{ |
| 6659 | | .op = .COMPARE_AND_WAIT, |
| 6660 | | .NO_ERRNO = true, |
| 6661 | | }; |
| 6662 | | try current_thread.beginSyscall(); |
| 6663 | | const status = if (darwin_supports_ulock_wait2) |
| 6664 | | c.__ulock_wait2(flags, ptr, expect, 0, 0) |
| 6665 | | else |
| 6666 | | c.__ulock_wait(flags, ptr, expect, 0); |
| 6667 | | current_thread.endSyscall(); |
| 6668 | | |
| 6669 | | if (status >= 0) return; |
| 6670 | | |
| 6671 | | if (is_debug) switch (@as(c.E, @enumFromInt(-status))) { |
| 6672 | | .INTR => {}, // spurious wake |
| 6673 | | // Address of the futex was paged out. This is unlikely, but possible in theory, and |
| 6674 | | // pthread/libdispatch on darwin bother to handle it. In this case we'll return |
| 6675 | | // without waiting, but the caller should retry anyway. |
| 6676 | | .FAULT => {}, |
| 6677 | | .TIMEDOUT => unreachable, |
| 6678 | | else => unreachable, |
| 6679 | | }; |
| 6680 | | }, |
| 6681 | | .windows => { |
| 6682 | | try current_thread.checkCancel(); |
| 6683 | | switch (windows.ntdll.RtlWaitOnAddress(ptr, &expect, @sizeOf(@TypeOf(expect)), null)) { |
| 6684 | | .SUCCESS => {}, |
| 6685 | | .CANCELLED => return error.Canceled, |
| 6686 | | else => recoverableOsBugDetected(), |
| 6687 | | } |
| 6688 | | }, |
| 6689 | | .freebsd => { |
| 6690 | | const flags = @intFromEnum(std.c.UMTX_OP.WAIT_UINT_PRIVATE); |
| 6691 | | try current_thread.beginSyscall(); |
| 6692 | | const rc = std.c._umtx_op(@intFromPtr(&ptr.raw), flags, @as(c_ulong, expect), 0, 0); |
| 6693 | | current_thread.endSyscall(); |
| 6694 | | if (is_debug) switch (posix.errno(rc)) { |
| 6695 | | .SUCCESS => {}, |
| 6696 | | .FAULT => unreachable, // one of the args points to invalid memory |
| 6697 | | .INVAL => unreachable, // arguments should be correct |
| 6698 | | .TIMEDOUT => unreachable, // no timeout provided |
| 6699 | | .INTR => {}, // spurious wake |
| 6700 | | else => unreachable, |
| 6701 | | }; |
| 6702 | | }, |
| 6703 | | else => @compileError("unimplemented: futexWait"), |
| 6704 | | } |
| 6705 | | } |
| 6706 | | |
| 6707 | | pub fn futexWaitUncancelable(ptr: *const std.atomic.Value(u32), expect: u32) void { |
| 6708 | | @branchHint(.cold); |
| 6709 | | |
| 6710 | | if (builtin.cpu.arch.isWasm()) { |
| 6711 | | comptime assert(builtin.cpu.has(.wasm, .atomics)); |
| 6712 | | const timeout: i64 = -1; |
| 6713 | | const signed_expect: i32 = @bitCast(expect); |
| 6714 | | const result = asm volatile ( |
| 6715 | | \\local.get %[ptr] |
| 6716 | | \\local.get %[expected] |
| 6717 | | \\local.get %[timeout] |
| 6718 | | \\memory.atomic.wait32 0 |
| 6719 | | \\local.set %[ret] |
| 6720 | | : [ret] "=r" (-> u32), |
| 6721 | | : [ptr] "r" (&ptr.raw), |
| 6722 | | [expected] "r" (signed_expect), |
| 6723 | | [timeout] "r" (timeout), |
| 6724 | | ); |
| 6725 | | switch (result) { |
| 6726 | | 0 => {}, // ok |
| 6727 | | 1 => {}, // expected != loaded |
| 6728 | | 2 => recoverableOsBugDetected(), // timeout |
| 6729 | | else => recoverableOsBugDetected(), |
| 6730 | | } |
| 6731 | | } else switch (native_os) { |
| 6732 | | .linux => { |
| 6733 | | const linux = std.os.linux; |
| 6734 | | const rc = linux.futex_4arg(ptr, .{ .cmd = .WAIT, .private = true }, expect, null); |
| 6735 | | switch (linux.errno(rc)) { |
| 6736 | | .SUCCESS => {}, // notified by `wake()` |
| 6737 | | .INTR => {}, // caller's responsibility to repeat |
| 6738 | | .AGAIN => {}, // ptr.* != expect |
| 6739 | | .INVAL => {}, // possibly timeout overflow |
| 6740 | | .TIMEDOUT => recoverableOsBugDetected(), |
| 6741 | | .FAULT => recoverableOsBugDetected(), // ptr was invalid |
| 6742 | | else => recoverableOsBugDetected(), |
| 6743 | | } |
| 6744 | | }, |
| 6745 | | .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { |
| 6746 | | const c = std.c; |
| 6747 | | const flags: c.UL = .{ |
| 6748 | | .op = .COMPARE_AND_WAIT, |
| 6749 | | .NO_ERRNO = true, |
| 6750 | | }; |
| 6751 | | const status = if (darwin_supports_ulock_wait2) |
| 6752 | | c.__ulock_wait2(flags, ptr, expect, 0, 0) |
| 6753 | | else |
| 6754 | | c.__ulock_wait(flags, ptr, expect, 0); |
| 6755 | | |
| 6756 | | if (status >= 0) return; |
| 6757 | | |
| 6758 | | switch (@as(c.E, @enumFromInt(-status))) { |
| 6759 | | // Wait was interrupted by the OS or other spurious signalling. |
| 6760 | | .INTR => {}, |
| 6761 | | // Address of the futex was paged out. This is unlikely, but possible in theory, and |
| 6762 | | // pthread/libdispatch on darwin bother to handle it. In this case we'll return |
| 6763 | | // without waiting, but the caller should retry anyway. |
| 6764 | | .FAULT => {}, |
| 6765 | | .TIMEDOUT => recoverableOsBugDetected(), |
| 6766 | | else => recoverableOsBugDetected(), |
| 6767 | | } |
| 6768 | | }, |
| 6769 | | .windows => { |
| 6770 | | switch (windows.ntdll.RtlWaitOnAddress(ptr, &expect, @sizeOf(@TypeOf(expect)), null)) { |
| 6771 | | .SUCCESS, .CANCELLED => {}, |
| 6772 | | else => recoverableOsBugDetected(), |
| 6773 | | } |
| 6774 | | }, |
| 6775 | | .freebsd => { |
| 6776 | | const flags = @intFromEnum(std.c.UMTX_OP.WAIT_UINT_PRIVATE); |
| 6777 | | const rc = std.c._umtx_op(@intFromPtr(&ptr.raw), flags, @as(c_ulong, expect), 0, 0); |
| 6778 | | switch (posix.errno(rc)) { |
| 6779 | | .SUCCESS => {}, |
| 6780 | | .INTR => {}, // spurious wake |
| 6781 | | .FAULT => recoverableOsBugDetected(), // one of the args points to invalid memory |
| 6782 | | .INVAL => recoverableOsBugDetected(), // arguments should be correct |
| 6783 | | .TIMEDOUT => recoverableOsBugDetected(), // no timeout provided |
| 6784 | | else => recoverableOsBugDetected(), |
| 6785 | | } |
| 6786 | | }, |
| 6787 | | else => @compileError("unimplemented: futexWaitUncancelable"), |
| 6788 | | } |
| 6789 | | } |
| 6790 | | |
| 6791 | | pub fn futexWake(ptr: *const std.atomic.Value(u32), max_waiters: u32) void { |
| 6792 | | @branchHint(.cold); |
| 6793 | | |
| 6794 | | if (builtin.cpu.arch.isWasm()) { |
| 6795 | | comptime assert(builtin.cpu.has(.wasm, .atomics)); |
| 6796 | | assert(max_waiters != 0); |
| 6797 | | const woken_count = asm volatile ( |
| 6798 | | \\local.get %[ptr] |
| 6799 | | \\local.get %[waiters] |
| 6800 | | \\memory.atomic.notify 0 |
| 6801 | | \\local.set %[ret] |
| 6802 | | : [ret] "=r" (-> u32), |
| 6803 | | : [ptr] "r" (&ptr.raw), |
| 6804 | | [waiters] "r" (max_waiters), |
| 6805 | | ); |
| 6806 | | _ = woken_count; // can be 0 when linker flag 'shared-memory' is not enabled |
| 6807 | | } else switch (native_os) { |
| 6808 | | .linux => { |
| 6809 | | const linux = std.os.linux; |
| 6810 | | switch (linux.errno(linux.futex_3arg( |
| 6811 | | &ptr.raw, |
| 6812 | | .{ .cmd = .WAKE, .private = true }, |
| 6813 | | @min(max_waiters, std.math.maxInt(i32)), |
| 6814 | | ))) { |
| 6815 | | .SUCCESS => return, // successful wake up |
| 6816 | | .INVAL => return, // invalid futex_wait() on ptr done elsewhere |
| 6817 | | .FAULT => return, // pointer became invalid while doing the wake |
| 6818 | | else => return recoverableOsBugDetected(), // deadlock due to operating system bug |
| 6819 | | } |
| 6820 | | }, |
| 6821 | | .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { |
| 6822 | | const c = std.c; |
| 6823 | | const flags: c.UL = .{ |
| 6824 | | .op = .COMPARE_AND_WAIT, |
| 6825 | | .NO_ERRNO = true, |
| 6826 | | .WAKE_ALL = max_waiters > 1, |
| 6827 | | }; |
| 6828 | | while (true) { |
| 6829 | | const status = c.__ulock_wake(flags, ptr, 0); |
| 6830 | | if (status >= 0) return; |
| 6831 | | switch (@as(c.E, @enumFromInt(-status))) { |
| 6832 | | .INTR, .CANCELED => continue, // spurious wake() |
| 6833 | | .FAULT => unreachable, // __ulock_wake doesn't generate EFAULT according to darwin pthread_cond_t |
| 6834 | | .NOENT => return, // nothing was woken up |
| 6835 | | .ALREADY => unreachable, // only for UL.Op.WAKE_THREAD |
| 6836 | | else => unreachable, // deadlock due to operating system bug |
| 6837 | | } |
| 6838 | | } |
| 6839 | | }, |
| 6840 | | .windows => { |
| 6841 | | assert(max_waiters != 0); |
| 6842 | | switch (max_waiters) { |
| 6843 | | 1 => windows.ntdll.RtlWakeAddressSingle(ptr), |
| 6844 | | else => windows.ntdll.RtlWakeAddressAll(ptr), |
| 6845 | | } |
| 6846 | | }, |
| 6847 | | .freebsd => { |
| 6848 | | const rc = std.c._umtx_op( |
| 6849 | | @intFromPtr(&ptr.raw), |
| 6850 | | @intFromEnum(std.c.UMTX_OP.WAKE_PRIVATE), |
| 6851 | | @as(c_ulong, max_waiters), |
| 6852 | | 0, // there is no timeout struct |
| 6853 | | 0, // there is no timeout struct pointer |
| 6854 | | ); |
| 6855 | | switch (posix.errno(rc)) { |
| 6856 | | .SUCCESS => {}, |
| 6857 | | .FAULT => {}, // it's ok if the ptr doesn't point to valid memory |
| 6858 | | .INVAL => unreachable, // arguments should be correct |
| 6859 | | else => unreachable, // deadlock due to operating system bug |
| 6860 | | } |
| 6861 | | }, |
| 6862 | | else => @compileError("unimplemented: futexWake"), |
| 6863 | | } |
| 6864 | | } |
| 6865 | | |
| 6866 | 6869 | /// A thread-safe logical boolean value which can be `set` and `unset`. |
| 6867 | 6870 | /// |
| 6868 | 6871 | /// It can also block threads until the value is set with cancelation via timed |
| ... | ... | @@ -6919,7 +6922,7 @@ const ResetEventFutex = enum(u32) { |
| 6919 | 6922 | } |
| 6920 | 6923 | const current_thread = Thread.getCurrent(t); |
| 6921 | 6924 | while (state == .waiting) { |
| 6922 | | try futexWait(current_thread, @ptrCast(ref), @intFromEnum(ResetEventFutex.waiting)); |
| 6925 | try current_thread.futexWait(@ptrCast(ref), @intFromEnum(ResetEventFutex.waiting)); |
| 6923 | 6926 | state = @atomicLoad(ResetEventFutex, ref, .acquire); |
| 6924 | 6927 | } |
| 6925 | 6928 | assert(state == .is_set); |
| ... | ... | @@ -6944,7 +6947,7 @@ const ResetEventFutex = enum(u32) { |
| 6944 | 6947 | state = @cmpxchgStrong(ResetEventFutex, ref, state, .waiting, .acquire, .acquire) orelse .waiting; |
| 6945 | 6948 | } |
| 6946 | 6949 | while (state == .waiting) { |
| 6947 | | futexWaitUncancelable(@ptrCast(ref), @intFromEnum(ResetEventFutex.waiting)); |
| 6950 | Thread.futexWaitUncancelable(@ptrCast(ref), @intFromEnum(ResetEventFutex.waiting)); |
| 6948 | 6951 | state = @atomicLoad(ResetEventFutex, ref, .acquire); |
| 6949 | 6952 | } |
| 6950 | 6953 | assert(state == .is_set); |
| ... | ... | @@ -6964,7 +6967,7 @@ const ResetEventFutex = enum(u32) { |
| 6964 | 6967 | return; |
| 6965 | 6968 | } |
| 6966 | 6969 | if (@atomicRmw(ResetEventFutex, ref, .Xchg, .is_set, .release) == .waiting) { |
| 6967 | | futexWake(@ptrCast(ref), std.math.maxInt(u32)); |
| 6970 | Thread.futexWake(@ptrCast(ref), std.math.maxInt(u32)); |
| 6968 | 6971 | } |
| 6969 | 6972 | } |
| 6970 | 6973 | |