std/sys/sync/thread_parking/pthread.rs
1//! Thread parking without `futex` using the `pthread` synchronization primitives.
2
3use crate::pin::Pin;
4use crate::sync::atomic::Ordering::{Acquire, Relaxed, Release};
5use crate::sync::atomic::{Atomic, AtomicUsize};
6use crate::sys::pal::sync::{Condvar, Mutex};
7use crate::time::Duration;
8
9const EMPTY: usize = 0;
10const PARKED: usize = 1;
11const NOTIFIED: usize = 2;
12
13pub struct Parker {
14 state: Atomic<usize>,
15 lock: Mutex,
16 cvar: Condvar,
17}
18
19impl Parker {
20 /// Constructs the UNIX parker in-place.
21 ///
22 /// # Safety
23 /// The constructed parker must never be moved.
24 pub unsafe fn new_in_place(parker: *mut Parker) {
25 parker.write(Parker {
26 state: AtomicUsize::new(EMPTY),
27 lock: Mutex::new(),
28 cvar: Condvar::new(),
29 });
30
31 Pin::new_unchecked(&mut (*parker).cvar).init();
32 }
33
34 fn lock(self: Pin<&Self>) -> Pin<&Mutex> {
35 unsafe { self.map_unchecked(|p| &p.lock) }
36 }
37
38 fn cvar(self: Pin<&Self>) -> Pin<&Condvar> {
39 unsafe { self.map_unchecked(|p| &p.cvar) }
40 }
41
42 // This implementation doesn't require `unsafe`, but other implementations
43 // may assume this is only called by the thread that owns the Parker.
44 //
45 // For memory ordering, see futex.rs
46 pub unsafe fn park(self: Pin<&Self>) {
47 // If we were previously notified then we consume this notification and
48 // return quickly.
49 if self.state.compare_exchange(NOTIFIED, EMPTY, Acquire, Relaxed).is_ok() {
50 return;
51 }
52
53 // Otherwise we need to coordinate going to sleep
54 self.lock().lock();
55 match self.state.compare_exchange(EMPTY, PARKED, Relaxed, Relaxed) {
56 Ok(_) => {}
57 Err(NOTIFIED) => {
58 // We must read here, even though we know it will be `NOTIFIED`.
59 // This is because `unpark` may have been called again since we read
60 // `NOTIFIED` in the `compare_exchange` above. We must perform an
61 // acquire operation that synchronizes with that `unpark` to observe
62 // any writes it made before the call to unpark. To do that we must
63 // read from the write it made to `state`.
64 let old = self.state.swap(EMPTY, Acquire);
65
66 self.lock().unlock();
67
68 assert_eq!(old, NOTIFIED, "park state changed unexpectedly");
69 return;
70 } // should consume this notification, so prohibit spurious wakeups in next park.
71 Err(_) => {
72 self.lock().unlock();
73
74 panic!("inconsistent park state")
75 }
76 }
77
78 loop {
79 self.cvar().wait(self.lock());
80
81 match self.state.compare_exchange(NOTIFIED, EMPTY, Acquire, Relaxed) {
82 Ok(_) => break, // got a notification
83 Err(_) => {} // spurious wakeup, go back to sleep
84 }
85 }
86
87 self.lock().unlock();
88 }
89
90 // This implementation doesn't require `unsafe`, but other implementations
91 // may assume this is only called by the thread that owns the Parker. Use
92 // `Pin` to guarantee a stable address for the mutex and condition variable.
93 pub unsafe fn park_timeout(self: Pin<&Self>, dur: Duration) {
94 // Like `park` above we have a fast path for an already-notified thread, and
95 // afterwards we start coordinating for a sleep.
96 // return quickly.
97 if self.state.compare_exchange(NOTIFIED, EMPTY, Acquire, Relaxed).is_ok() {
98 return;
99 }
100
101 self.lock().lock();
102 match self.state.compare_exchange(EMPTY, PARKED, Relaxed, Relaxed) {
103 Ok(_) => {}
104 Err(NOTIFIED) => {
105 // We must read again here, see `park`.
106 let old = self.state.swap(EMPTY, Acquire);
107 self.lock().unlock();
108
109 assert_eq!(old, NOTIFIED, "park state changed unexpectedly");
110 return;
111 } // should consume this notification, so prohibit spurious wakeups in next park.
112 Err(_) => {
113 self.lock().unlock();
114 panic!("inconsistent park_timeout state")
115 }
116 }
117
118 // Wait with a timeout, and if we spuriously wake up or otherwise wake up
119 // from a notification we just want to unconditionally set the state back to
120 // empty, either consuming a notification or un-flagging ourselves as
121 // parked.
122 self.cvar().wait_timeout(self.lock(), dur);
123
124 match self.state.swap(EMPTY, Acquire) {
125 NOTIFIED => self.lock().unlock(), // got a notification, hurray!
126 PARKED => self.lock().unlock(), // no notification, alas
127 n => {
128 self.lock().unlock();
129 panic!("inconsistent park_timeout state: {n}")
130 }
131 }
132 }
133
134 pub fn unpark(self: Pin<&Self>) {
135 // To ensure the unparked thread will observe any writes we made
136 // before this call, we must perform a release operation that `park`
137 // can synchronize with. To do that we must write `NOTIFIED` even if
138 // `state` is already `NOTIFIED`. That is why this must be a swap
139 // rather than a compare-and-swap that returns if it reads `NOTIFIED`
140 // on failure.
141 match self.state.swap(NOTIFIED, Release) {
142 EMPTY => return, // no one was waiting
143 NOTIFIED => return, // already unparked
144 PARKED => {} // gotta go wake someone up
145 _ => panic!("inconsistent state in unpark"),
146 }
147
148 // There is a period between when the parked thread sets `state` to
149 // `PARKED` (or last checked `state` in the case of a spurious wake
150 // up) and when it actually waits on `cvar`. If we were to notify
151 // during this period it would be ignored and then when the parked
152 // thread went to sleep it would never wake up. Fortunately, it has
153 // `lock` locked at this stage so we can acquire `lock` to wait until
154 // it is ready to receive the notification.
155 //
156 // Releasing `lock` before the call to `notify_one` means that when the
157 // parked thread wakes it doesn't get woken only to have to wait for us
158 // to release `lock`.
159 unsafe {
160 self.lock().lock();
161 self.lock().unlock();
162 self.cvar().notify_one();
163 }
164 }
165}