| ... | @@ -180,23 +180,36 @@ const DarwinFutex = struct { | ... | @@ -180,23 +180,36 @@ const DarwinFutex = struct { |
| 180 | const darwin = std.os.darwin; | 180 | const darwin = std.os.darwin; |
| 181 | | 181 | |
| 182 | fn wait(ptr: *const Atomic(u32), expect: u32, timeout: ?u64) error{TimedOut}!void { | 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 | // Darwin XNU 7195.50.7.100.1 introduced __ulock_wait2 and migrated code paths (notably pthread_cond_t) towards it: | 183 | // Darwin XNU 7195.50.7.100.1 introduced __ulock_wait2 and migrated code paths (notably pthread_cond_t) towards it: |
| 190 | // https://github.com/apple/darwin-xnu/commit/d4061fb0260b3ed486147341b72468f836ed6c8f#diff-08f993cc40af475663274687b7c326cc6c3031e0db3ac8de7b24624610616be6 | 184 | // https://github.com/apple/darwin-xnu/commit/d4061fb0260b3ed486147341b72468f836ed6c8f#diff-08f993cc40af475663274687b7c326cc6c3031e0db3ac8de7b24624610616be6 |
| 191 | // | 185 | // |
| 192 | // This XNU version appears to correspond to 11.0.1: | 186 | // This XNU version appears to correspond to 11.0.1: |
| 193 | // https://kernelshaman.blogspot.com/2021/01/building-xnu-for-macos-big-sur-1101.html | 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 | const addr = @ptrCast(*const c_void, ptr); | 197 | const addr = @ptrCast(*const c_void, ptr); |
| 195 | const flags = darwin.UL_COMPARE_AND_WAIT | darwin.ULF_NO_ERRNO; | 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 | const status = blk: { | 205 | const status = blk: { |
| 197 | if (target.os.version_range.semver.max.major >= 11) { | 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 | } else { | 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 | break :blk darwin.__ulock_wait(flags, addr, expect, timeout_us); | 213 | break :blk darwin.__ulock_wait(flags, addr, expect, timeout_us); |
| 201 | } | 214 | } |
| 202 | }; | 215 | }; |
| ... | @@ -204,8 +217,11 @@ const DarwinFutex = struct { | ... | @@ -204,8 +217,11 @@ const DarwinFutex = struct { |
| 204 | if (status >= 0) return; | 217 | if (status >= 0) return; |
| 205 | switch (-status) { | 218 | switch (-status) { |
| 206 | darwin.EINTR => {}, | 219 | darwin.EINTR => {}, |
| 207 | darwin.EFAULT => unreachable, | 220 | // Address of the futex is paged out. This is unlikely, but possible in theory, and |
| 208 | darwin.ETIMEDOUT => return error.TimedOut, | 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 | else => unreachable, | 225 | else => unreachable, |
| 210 | } | 226 | } |
| 211 | } | 227 | } |
| ... | @@ -223,6 +239,7 @@ const DarwinFutex = struct { | ... | @@ -223,6 +239,7 @@ const DarwinFutex = struct { |
| 223 | if (status >= 0) return; | 239 | if (status >= 0) return; |
| 224 | switch (-status) { | 240 | switch (-status) { |
| 225 | darwin.EINTR => continue, // spurious wake() | 241 | darwin.EINTR => continue, // spurious wake() |
| | 242 | darwin.EFAULT => continue, // address of the lock was paged out |
| 226 | darwin.ENOENT => return, // nothing was woken up | 243 | darwin.ENOENT => return, // nothing was woken up |
| 227 | darwin.EALREADY => unreachable, // only for ULF_WAKE_THREAD | 244 | darwin.EALREADY => unreachable, // only for ULF_WAKE_THREAD |
| 228 | else => unreachable, | 245 | else => unreachable, |