]>
Commit | Line | Data |
---|---|---|
532ac7d7 | 1 | use crate::cell::UnsafeCell; |
6522a427 EL |
2 | use crate::ptr; |
3 | use crate::sync::atomic::{AtomicPtr, Ordering::Relaxed}; | |
5e7ed085 | 4 | use crate::sys::locks::{pthread_mutex, Mutex}; |
6522a427 | 5 | use crate::sys::time::TIMESPEC_MAX; |
923072b8 | 6 | use crate::sys_common::lazy_box::{LazyBox, LazyInit}; |
532ac7d7 | 7 | use crate::time::Duration; |
1a4d82fc | 8 | |
6522a427 EL |
9 | struct AllocatedCondvar(UnsafeCell<libc::pthread_cond_t>); |
10 | ||
dfeec247 | 11 | pub struct Condvar { |
6522a427 EL |
12 | inner: LazyBox<AllocatedCondvar>, |
13 | mutex: AtomicPtr<libc::pthread_mutex_t>, | |
dfeec247 | 14 | } |
1a4d82fc | 15 | |
6522a427 EL |
16 | #[inline] |
17 | fn raw(c: &Condvar) -> *mut libc::pthread_cond_t { | |
18 | c.inner.0.get() | |
19 | } | |
29967ef6 | 20 | |
6522a427 EL |
21 | unsafe impl Send for AllocatedCondvar {} |
22 | unsafe impl Sync for AllocatedCondvar {} | |
c34b1796 | 23 | |
6522a427 EL |
24 | impl LazyInit for AllocatedCondvar { |
25 | fn init() -> Box<Self> { | |
26 | let condvar = Box::new(AllocatedCondvar(UnsafeCell::new(libc::PTHREAD_COND_INITIALIZER))); | |
27 | ||
28 | cfg_if::cfg_if! { | |
29 | if #[cfg(any( | |
30 | target_os = "macos", | |
31 | target_os = "ios", | |
32 | target_os = "watchos", | |
33 | target_os = "l4re", | |
34 | target_os = "android", | |
35 | target_os = "redox" | |
36 | ))] { | |
37 | // `pthread_condattr_setclock` is unfortunately not supported on these platforms. | |
38 | } else if #[cfg(any(target_os = "espidf", target_os = "horizon"))] { | |
39 | // NOTE: ESP-IDF's PTHREAD_COND_INITIALIZER support is not released yet | |
40 | // So on that platform, init() should always be called | |
41 | // Moreover, that platform does not have pthread_condattr_setclock support, | |
42 | // hence that initialization should be skipped as well | |
43 | // | |
44 | // Similar story for the 3DS (horizon). | |
45 | let r = unsafe { libc::pthread_cond_init(condvar.0.get(), crate::ptr::null()) }; | |
46 | assert_eq!(r, 0); | |
47 | } else { | |
48 | use crate::mem::MaybeUninit; | |
49 | let mut attr = MaybeUninit::<libc::pthread_condattr_t>::uninit(); | |
50 | let r = unsafe { libc::pthread_condattr_init(attr.as_mut_ptr()) }; | |
51 | assert_eq!(r, 0); | |
52 | let r = unsafe { libc::pthread_condattr_setclock(attr.as_mut_ptr(), libc::CLOCK_MONOTONIC) }; | |
53 | assert_eq!(r, 0); | |
54 | let r = unsafe { libc::pthread_cond_init(condvar.0.get(), attr.as_ptr()) }; | |
55 | assert_eq!(r, 0); | |
56 | let r = unsafe { libc::pthread_condattr_destroy(attr.as_mut_ptr()) }; | |
57 | assert_eq!(r, 0); | |
58 | } | |
59 | } | |
9e0c209e | 60 | |
6522a427 EL |
61 | condvar |
62 | } | |
041b39d2 XL |
63 | } |
64 | ||
6522a427 EL |
65 | impl Drop for AllocatedCondvar { |
66 | #[inline] | |
67 | fn drop(&mut self) { | |
68 | let r = unsafe { libc::pthread_cond_destroy(self.0.get()) }; | |
69 | if cfg!(target_os = "dragonfly") { | |
70 | // On DragonFly pthread_cond_destroy() returns EINVAL if called on | |
71 | // a condvar that was just initialized with | |
72 | // libc::PTHREAD_COND_INITIALIZER. Once it is used or | |
73 | // pthread_cond_init() is called, this behaviour no longer occurs. | |
74 | debug_assert!(r == 0 || r == libc::EINVAL); | |
75 | } else { | |
76 | debug_assert_eq!(r, 0); | |
77 | } | |
923072b8 FG |
78 | } |
79 | } | |
80 | ||
1a4d82fc | 81 | impl Condvar { |
62682a34 | 82 | pub const fn new() -> Condvar { |
6522a427 | 83 | Condvar { inner: LazyBox::new(), mutex: AtomicPtr::new(ptr::null_mut()) } |
94222f64 XL |
84 | } |
85 | ||
6522a427 EL |
86 | #[inline] |
87 | fn verify(&self, mutex: *mut libc::pthread_mutex_t) { | |
88 | // Relaxed is okay here because we never read through `self.addr`, and only use it to | |
89 | // compare addresses. | |
90 | match self.mutex.compare_exchange(ptr::null_mut(), mutex, Relaxed, Relaxed) { | |
91 | Ok(_) => {} // Stored the address | |
92 | Err(n) if n == mutex => {} // Lost a race to store the same address | |
93 | _ => panic!("attempted to use a condition variable with two mutexes"), | |
94 | } | |
9e0c209e SL |
95 | } |
96 | ||
1a4d82fc | 97 | #[inline] |
6522a427 EL |
98 | pub fn notify_one(&self) { |
99 | let r = unsafe { libc::pthread_cond_signal(raw(self)) }; | |
1a4d82fc JJ |
100 | debug_assert_eq!(r, 0); |
101 | } | |
102 | ||
103 | #[inline] | |
6522a427 EL |
104 | pub fn notify_all(&self) { |
105 | let r = unsafe { libc::pthread_cond_broadcast(raw(self)) }; | |
1a4d82fc JJ |
106 | debug_assert_eq!(r, 0); |
107 | } | |
108 | ||
109 | #[inline] | |
110 | pub unsafe fn wait(&self, mutex: &Mutex) { | |
6522a427 EL |
111 | let mutex = pthread_mutex::raw(mutex); |
112 | self.verify(mutex); | |
113 | let r = libc::pthread_cond_wait(raw(self), mutex); | |
1a4d82fc JJ |
114 | debug_assert_eq!(r, 0); |
115 | } | |
116 | ||
9e0c209e SL |
117 | // This implementation is used on systems that support pthread_condattr_setclock |
118 | // where we configure condition variable to use monotonic clock (instead of | |
119 | // default system clock). This approach avoids all problems that result | |
120 | // from changes made to the system time. | |
94222f64 XL |
121 | #[cfg(not(any( |
122 | target_os = "macos", | |
123 | target_os = "ios", | |
064997fb | 124 | target_os = "watchos", |
94222f64 | 125 | target_os = "android", |
923072b8 FG |
126 | target_os = "espidf", |
127 | target_os = "horizon" | |
94222f64 | 128 | )))] |
9e0c209e | 129 | pub unsafe fn wait_timeout(&self, mutex: &Mutex, dur: Duration) -> bool { |
6522a427 | 130 | use crate::sys::time::Timespec; |
9e0c209e | 131 | |
6522a427 EL |
132 | let mutex = pthread_mutex::raw(mutex); |
133 | self.verify(mutex); | |
abe05a73 | 134 | |
6522a427 EL |
135 | let timeout = Timespec::now(libc::CLOCK_MONOTONIC) |
136 | .checked_add_duration(&dur) | |
137 | .and_then(|t| t.to_timespec()) | |
138 | .unwrap_or(TIMESPEC_MAX); | |
139 | let r = libc::pthread_cond_timedwait(raw(self), mutex, &timeout); | |
9e0c209e SL |
140 | assert!(r == libc::ETIMEDOUT || r == 0); |
141 | r == 0 | |
142 | } | |
143 | ||
85aaf69f SL |
144 | // This implementation is modeled after libcxx's condition_variable |
145 | // https://github.com/llvm-mirror/libcxx/blob/release_35/src/condition_variable.cpp#L46 | |
146 | // https://github.com/llvm-mirror/libcxx/blob/release_35/include/__mutex_base#L367 | |
94222f64 XL |
147 | #[cfg(any( |
148 | target_os = "macos", | |
149 | target_os = "ios", | |
064997fb | 150 | target_os = "watchos", |
94222f64 | 151 | target_os = "android", |
923072b8 FG |
152 | target_os = "espidf", |
153 | target_os = "horizon" | |
94222f64 | 154 | ))] |
6522a427 EL |
155 | pub unsafe fn wait_timeout(&self, mutex: &Mutex, dur: Duration) -> bool { |
156 | use crate::sys::time::SystemTime; | |
532ac7d7 | 157 | use crate::time::Instant; |
9e0c209e | 158 | |
6522a427 EL |
159 | let mutex = pthread_mutex::raw(mutex); |
160 | self.verify(mutex); | |
161 | ||
162 | // OSX implementation of `pthread_cond_timedwait` is buggy | |
163 | // with super long durations. When duration is greater than | |
164 | // 0x100_0000_0000_0000 seconds, `pthread_cond_timedwait` | |
165 | // in macOS Sierra returns error 316. | |
166 | // | |
167 | // This program demonstrates the issue: | |
168 | // https://gist.github.com/stepancheg/198db4623a20aad2ad7cddb8fda4a63c | |
169 | // | |
170 | // To work around this issue, and possible bugs of other OSes, timeout | |
171 | // is clamped to 1000 years, which is allowable per the API of `wait_timeout` | |
172 | // because of spurious wakeups. | |
173 | let dur = Duration::min(dur, Duration::from_secs(1000 * 365 * 86400)); | |
174 | ||
175 | // pthread_cond_timedwait uses system time, but we want to report timeout | |
176 | // based on stable time. | |
177 | let now = Instant::now(); | |
178 | ||
179 | let timeout = SystemTime::now() | |
180 | .t | |
181 | .checked_add_duration(&dur) | |
182 | .and_then(|t| t.to_timespec()) | |
dfeec247 | 183 | .unwrap_or(TIMESPEC_MAX); |
1a4d82fc | 184 | |
6522a427 | 185 | let r = libc::pthread_cond_timedwait(raw(self), mutex, &timeout); |
85aaf69f SL |
186 | debug_assert!(r == libc::ETIMEDOUT || r == 0); |
187 | ||
d9579d0f AL |
188 | // ETIMEDOUT is not a totally reliable method of determining timeout due |
189 | // to clock shifts, so do the check ourselves | |
6522a427 | 190 | now.elapsed() < dur |
923072b8 FG |
191 | } |
192 | } |