| ... | ... | @@ -180,23 +180,36 @@ const DarwinFutex = struct { |
| 180 | 180 | const darwin = std.os.darwin; |
| 181 | 181 | |
| 182 | 182 | fn wait(ptr: *const Atomic(u32), expect: u32, timeout: ?u64) error{TimedOut}!void { |
| 183 | | // ulock_wait() uses micro-second timeouts, where 0 = INIFITE or no-timeout |
| 184 | | var timeout_us: u32 = 0; |
| 185 | | if (timeout) |timeout_ns| { |
| 186 | | timeout_us = @intCast(u32, timeout_ns / std.time.ns_per_us); |
| 187 | | } |
| 188 | | |
| 189 | 183 | // Darwin XNU 7195.50.7.100.1 introduced __ulock_wait2 and migrated code paths (notably pthread_cond_t) towards it: |
| 190 | 184 | // https://github.com/apple/darwin-xnu/commit/d4061fb0260b3ed486147341b72468f836ed6c8f#diff-08f993cc40af475663274687b7c326cc6c3031e0db3ac8de7b24624610616be6 |
| 191 | 185 | // |
| 192 | 186 | // This XNU version appears to correspond to 11.0.1: |
| 193 | 187 | // https://kernelshaman.blogspot.com/2021/01/building-xnu-for-macos-big-sur-1101.html |
| 188 | // |
| 189 | // ulock_wait() uses 32-bit micro-second timeouts where 0 = INFINITE or no-timeout |
| 190 | // ulock_wait2() uses 64-bit nano-second timeouts (with the same convention) |
| 191 | var timeout_ns: u64 = 0; |
| 192 | if (timeout) |timeout_value| { |
| 193 | // This should be checked by the caller. |
| 194 | assert(timeout_value != 0); |
| 195 | timeout_ns = timeout_value; |
| 196 | } |
| 194 | 197 | const addr = @ptrCast(*const c_void, ptr); |
| 195 | 198 | const flags = darwin.UL_COMPARE_AND_WAIT | darwin.ULF_NO_ERRNO; |
| 199 | // If we're using `__ulock_wait` and `timeout` is too big to fit inside a `u32` count of |
| 200 | // micro-seconds (around 70min), we'll request a shorter timeout. This is fine (users |
| 201 | // should handle spurious wakeups), but we need to remember that we did so, so that |
| 202 | // we don't return `TimedOut` incorrectly. If that happens, we set this variable to |
| 203 | // true so that we we know to ignore the ETIMEDOUT result. |
| 204 | var timeout_overflowed = false; |
| 196 | 205 | const status = blk: { |
| 197 | 206 | if (target.os.version_range.semver.max.major >= 11) { |
| 198 | | break :blk darwin.__ulock_wait2(flags, addr, expect, timeout_us, 0); |
| 207 | break :blk darwin.__ulock_wait2(flags, addr, expect, timeout_ns, 0); |
| 199 | 208 | } else { |
| 209 | const timeout_us = std.math.cast(u32, timeout_ns / std.time.ns_per_us) catch overflow: { |
| 210 | timeout_overflowed = true; |
| 211 | break :overflow std.math.maxInt(u32); |
| 212 | }; |
| 200 | 213 | break :blk darwin.__ulock_wait(flags, addr, expect, timeout_us); |
| 201 | 214 | } |
| 202 | 215 | }; |
| ... | ... | @@ -204,8 +217,11 @@ const DarwinFutex = struct { |
| 204 | 217 | if (status >= 0) return; |
| 205 | 218 | switch (-status) { |
| 206 | 219 | darwin.EINTR => {}, |
| 207 | | darwin.EFAULT => unreachable, |
| 208 | | darwin.ETIMEDOUT => return error.TimedOut, |
| 220 | // Address of the futex is paged out. This is unlikely, but possible in theory, and |
| 221 | // pthread/libdispatch on darwin bother to handle it. In this case we'll return |
| 222 | // without waiting, but the caller should retry anyway. |
| 223 | darwin.EFAULT => {}, |
| 224 | darwin.ETIMEDOUT => if (!timeout_overflowed) return error.TimedOut, |
| 209 | 225 | else => unreachable, |
| 210 | 226 | } |
| 211 | 227 | } |
| ... | ... | @@ -223,6 +239,7 @@ const DarwinFutex = struct { |
| 223 | 239 | if (status >= 0) return; |
| 224 | 240 | switch (-status) { |
| 225 | 241 | darwin.EINTR => continue, // spurious wake() |
| 242 | darwin.EFAULT => continue, // address of the lock was paged out |
| 226 | 243 | darwin.ENOENT => return, // nothing was woken up |
| 227 | 244 | darwin.EALREADY => unreachable, // only for ULF_WAKE_THREAD |
| 228 | 245 | else => unreachable, |