std/sys/sync/thread_parking/
darwin.rs

1//! Thread parking for Darwin-based systems.
2//!
3//! Darwin actually has futex syscalls (`__ulock_wait`/`__ulock_wake`), but they
4//! cannot be used in `std` because they are non-public (their use will lead to
5//! rejection from the App Store).
6//!
7//! Therefore, we need to look for other synchronization primitives. Luckily, Darwin
8//! supports semaphores, which allow us to implement the behavior we need with
9//! only one primitive (as opposed to a mutex-condvar pair). We use the semaphore
10//! provided by libdispatch, as the underlying Mach semaphore is only dubiously
11//! public.
12
13#![allow(non_camel_case_types)]
14
15use crate::pin::Pin;
16use crate::sync::atomic::Ordering::{Acquire, Release};
17use crate::sync::atomic::{Atomic, AtomicI8};
18use crate::time::Duration;
19
20type dispatch_semaphore_t = *mut crate::ffi::c_void;
21type dispatch_time_t = u64;
22
23const DISPATCH_TIME_NOW: dispatch_time_t = 0;
24const DISPATCH_TIME_FOREVER: dispatch_time_t = !0;
25
26// Contained in libSystem.dylib, which is linked by default.
27unsafe extern "C" {
28    fn dispatch_time(when: dispatch_time_t, delta: i64) -> dispatch_time_t;
29    fn dispatch_semaphore_create(val: isize) -> dispatch_semaphore_t;
30    fn dispatch_semaphore_wait(dsema: dispatch_semaphore_t, timeout: dispatch_time_t) -> isize;
31    fn dispatch_semaphore_signal(dsema: dispatch_semaphore_t) -> isize;
32    fn dispatch_release(object: *mut crate::ffi::c_void);
33}
34
35const EMPTY: i8 = 0;
36const NOTIFIED: i8 = 1;
37const PARKED: i8 = -1;
38
39pub struct Parker {
40    semaphore: dispatch_semaphore_t,
41    state: Atomic<i8>,
42}
43
44unsafe impl Sync for Parker {}
45unsafe impl Send for Parker {}
46
47impl Parker {
48    pub unsafe fn new_in_place(parker: *mut Parker) {
49        let semaphore = dispatch_semaphore_create(0);
50        assert!(
51            !semaphore.is_null(),
52            "failed to create dispatch semaphore for thread synchronization"
53        );
54        parker.write(Parker { semaphore, state: AtomicI8::new(EMPTY) })
55    }
56
57    // Does not need `Pin`, but other implementation do.
58    pub unsafe fn park(self: Pin<&Self>) {
59        // The semaphore counter must be zero at this point, because unparking
60        // threads will not actually increase it until we signalled that we
61        // are waiting.
62
63        // Change NOTIFIED to EMPTY and EMPTY to PARKED.
64        if self.state.fetch_sub(1, Acquire) == NOTIFIED {
65            return;
66        }
67
68        // Another thread may increase the semaphore counter from this point on.
69        // If it is faster than us, we will decrement it again immediately below.
70        // If we are faster, we wait.
71
72        // Ensure that the semaphore counter has actually been decremented, even
73        // if the call timed out for some reason.
74        while dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER) != 0 {}
75
76        // At this point, the semaphore counter is zero again.
77
78        // We were definitely woken up, so we don't need to check the state.
79        // Still, we need to reset the state using a swap to observe the state
80        // change with acquire ordering.
81        self.state.swap(EMPTY, Acquire);
82    }
83
84    // Does not need `Pin`, but other implementation do.
85    pub unsafe fn park_timeout(self: Pin<&Self>, dur: Duration) {
86        if self.state.fetch_sub(1, Acquire) == NOTIFIED {
87            return;
88        }
89
90        let nanos = dur.as_nanos().try_into().unwrap_or(i64::MAX);
91        let timeout = dispatch_time(DISPATCH_TIME_NOW, nanos);
92
93        let timeout = dispatch_semaphore_wait(self.semaphore, timeout) != 0;
94
95        let state = self.state.swap(EMPTY, Acquire);
96        if state == NOTIFIED && timeout {
97            // If the state was NOTIFIED but semaphore_wait returned without
98            // decrementing the count because of a timeout, it means another
99            // thread is about to call semaphore_signal. We must wait for that
100            // to happen to ensure the semaphore count is reset.
101            while dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER) != 0 {}
102        } else {
103            // Either a timeout occurred and we reset the state before any thread
104            // tried to wake us up, or we were woken up and reset the state,
105            // making sure to observe the state change with acquire ordering.
106            // Either way, the semaphore counter is now zero again.
107        }
108    }
109
110    // Does not need `Pin`, but other implementation do.
111    pub fn unpark(self: Pin<&Self>) {
112        let state = self.state.swap(NOTIFIED, Release);
113        if state == PARKED {
114            unsafe {
115                dispatch_semaphore_signal(self.semaphore);
116            }
117        }
118    }
119}
120
121impl Drop for Parker {
122    fn drop(&mut self) {
123        // SAFETY:
124        // We always ensure that the semaphore count is reset, so this will
125        // never cause an exception.
126        unsafe {
127            dispatch_release(self.semaphore);
128        }
129    }
130}