@@ -180,32 +180,48 @@ const DarwinFutex = struct {
180180 const darwin = std .os .darwin ;
181181
182182 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-
189183 // Darwin XNU 7195.50.7.100.1 introduced __ulock_wait2 and migrated code paths (notably pthread_cond_t) towards it:
190184 // https://github.com/apple/darwin-xnu/commit/d4061fb0260b3ed486147341b72468f836ed6c8f#diff-08f993cc40af475663274687b7c326cc6c3031e0db3ac8de7b24624610616be6
191185 //
192186 // This XNU version appears to correspond to 11.0.1:
193187 // 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+ }
194197 const addr = @ptrCast (* const c_void , ptr );
195198 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 ;
196205 const status = blk : {
197206 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 );
199208 } 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+ };
200213 break :blk darwin .__ulock_wait (flags , addr , expect , timeout_us );
201214 }
202215 };
203216
204217 if (status >= 0 ) return ;
205218 switch (- status ) {
206219 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 ,
209225 else = > unreachable ,
210226 }
211227 }
@@ -223,6 +239,7 @@ const DarwinFutex = struct {
223239 if (status >= 0 ) return ;
224240 switch (- status ) {
225241 darwin .EINTR = > continue , // spurious wake()
242+ darwin .EFAULT = > continue , // address of the lock was paged out
226243 darwin .ENOENT = > return , // nothing was woken up
227244 darwin .EALREADY = > unreachable , // only for ULF_WAKE_THREAD
228245 else = > unreachable ,
0 commit comments