fn futexWaitInner(ptr: *const u32, expect: u32, uncancelable: bool, timeout_ns: ?u64) Io.Cancelable!void {
@branchHint(.cold);
if (builtin.single_threaded) unreachable; // nobody would ever wake us
if (use_parking_futex) {
return parking_futex.wait(
ptr,
expect,
uncancelable,
if (timeout_ns) |ns| .{ .duration = .{
.raw = .fromNanoseconds(ns),
.clock = .boot,
} } else .none,
);
} else if (builtin.cpu.arch.isWasm()) {
comptime assert(builtin.cpu.has(.wasm, .atomics));
// TODO implement cancelation for WASM futex waits by signaling the futex
if (!uncancelable) try Thread.checkCancel();
const to: i64 = if (timeout_ns) |ns| std.math.cast(i64, ns) orelse std.math.maxInt(i64) else -1;
const signed_expect: i32 = @bitCast(expect);
const result = asm volatile (
\\local.get %[ptr]
\\local.get %[expected]
\\local.get %[timeout]
\\memory.atomic.wait32 0
\\local.set %[ret]
: [ret] "=r" (-> u32),
: [ptr] "r" (ptr),
[expected] "r" (signed_expect),
[timeout] "r" (to),
);
switch (result) {
0 => {}, // ok
1 => {}, // expected != loaded
2 => {}, // timeout
else => assert(!is_debug),
}
} else switch (native_os) {
.linux => {
const linux = std.os.linux;
var ts_buffer: linux.timespec = undefined;
const ts: ?*linux.timespec = if (timeout_ns) |ns| ts: {
ts_buffer = timestampToPosix(ns);
break :ts &ts_buffer;
} else null;
const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start();
const rc = linux.futex_4arg(ptr, .{ .cmd = .WAIT, .private = true }, expect, ts);
syscall.finish();
switch (linux.errno(rc)) {
.SUCCESS => {}, // notified by `wake()`
.INTR => {}, // caller's responsibility to retry
.AGAIN => {}, // ptr.* != expect
.INVAL => {}, // possibly timeout overflow
.TIMEDOUT => {},
.FAULT => recoverableOsBugDetected(), // ptr was invalid
else => recoverableOsBugDetected(),
}
},
.driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
const c = std.c;
const flags: c.UL = .{
.op = .COMPARE_AND_WAIT,
.NO_ERRNO = true,
};
const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start();
const status = switch (darwin_supports_ulock_wait2) {
true => c.__ulock_wait2(flags, ptr, expect, ns: {
const ns = timeout_ns orelse break :ns 0;
if (ns == 0) break :ns 1;
break :ns ns;
}, 0),
false => c.__ulock_wait(flags, ptr, expect, us: {
const ns = timeout_ns orelse break :us 0;
const us = std.math.lossyCast(u32, ns / std.time.ns_per_us);
if (us == 0) break :us 1;
break :us us;
}),
};
syscall.finish();
if (status >= 0) return;
switch (@as(c.E, @fromBackingInt(@intCast(-status)))) {
.INTR => {}, // spurious wake
// Address of the futex was paged out. This is unlikely, but possible in theory, and
// pthread/libdispatch on darwin bother to handle it. In this case we'll return
// without waiting, but the caller should retry anyway.
.FAULT => {},
.TIMEDOUT => {}, // timeout
else => recoverableOsBugDetected(),
}
},
.freebsd => {
const flags = @backingInt(std.c.UMTX_OP.WAIT_UINT_PRIVATE);
var tm_size: usize = 0;
var tm: std.c._umtx_time = undefined;
var tm_ptr: ?*const std.c._umtx_time = null;
if (timeout_ns) |ns| {
tm_ptr = &tm;
tm_size = @sizeOf(@TypeOf(tm));
tm.flags = 0; // use relative time not UMTX_ABSTIME
tm.clockid = .MONOTONIC;
tm.timeout = timestampToPosix(ns);
}
const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start();
const rc = std.c._umtx_op(@intFromPtr(ptr), flags, @as(c_ulong, expect), tm_size, @intFromPtr(tm_ptr));
syscall.finish();
if (is_debug) switch (posix.errno(rc)) {
.SUCCESS => {},
.FAULT => unreachable, // one of the args points to invalid memory
.INVAL => unreachable, // arguments should be correct
.TIMEDOUT => {}, // timeout
.INTR => {}, // spurious wake
else => unreachable,
};
},
.openbsd => {
var tm: std.c.timespec = undefined;
var tm_ptr: ?*const std.c.timespec = null;
if (timeout_ns) |ns| {
tm_ptr = &tm;
tm = timestampToPosix(ns);
}
const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start();
const rc = std.c.futex(
ptr,
std.c.FUTEX.WAIT | std.c.FUTEX.PRIVATE_FLAG,
@as(c_int, @bitCast(expect)),
tm_ptr,
null, // uaddr2 is ignored
);
syscall.finish();
if (is_debug) switch (posix.errno(rc)) {
.SUCCESS => {},
.NOSYS => unreachable, // constant op known good value
.AGAIN => {}, // contents of uaddr != val
.INVAL => unreachable, // invalid timeout
.TIMEDOUT => {}, // timeout
.INTR => {}, // a signal arrived
.CANCELED => {}, // a signal arrived and SA_RESTART was set
else => unreachable,
};
},
.dragonfly => {
var timeout_us: c_int = undefined;
if (timeout_ns) |ns| {
timeout_us = std.math.cast(c_int, ns / std.time.ns_per_us) orelse std.math.maxInt(c_int);
} else {
timeout_us = 0;
}
const syscall: Syscall = if (uncancelable) .{ .thread = null } else try .start();
const rc = std.c.umtx_sleep(@ptrCast(ptr), @bitCast(expect), timeout_us);
syscall.finish();
if (is_debug) switch (std.posix.errno(rc)) {
.SUCCESS => {},
.BUSY => {}, // ptr != expect
.AGAIN => {}, // maybe timed out, or paged out, or hit 2s kernel refresh
.INTR => {}, // spurious wake
.INVAL => unreachable, // invalid timeout
else => unreachable,
};
},
else => @compileError("unimplemented: futexWait"),
}
}