]> git.lizzy.rs Git - rust.git/blob - library/std/src/sys/unix/locks/pthread_condvar.rs
Rollup merge of #104258 - compiler-errors:tait-closure-deduce, r=oli-obk
[rust.git] / library / std / src / sys / unix / locks / pthread_condvar.rs
1 use crate::cell::UnsafeCell;
2 use crate::ptr;
3 use crate::sync::atomic::{AtomicPtr, Ordering::Relaxed};
4 use crate::sys::locks::{pthread_mutex, Mutex};
5 use crate::sys_common::lazy_box::{LazyBox, LazyInit};
6 use crate::time::Duration;
7
8 struct AllocatedCondvar(UnsafeCell<libc::pthread_cond_t>);
9
10 pub struct Condvar {
11     inner: LazyBox<AllocatedCondvar>,
12     mutex: AtomicPtr<libc::pthread_mutex_t>,
13 }
14
15 const TIMESPEC_MAX: libc::timespec =
16     libc::timespec { tv_sec: <libc::time_t>::MAX, tv_nsec: 1_000_000_000 - 1 };
17
18 fn saturating_cast_to_time_t(value: u64) -> libc::time_t {
19     if value > <libc::time_t>::MAX as u64 { <libc::time_t>::MAX } else { value as libc::time_t }
20 }
21
22 #[inline]
23 fn raw(c: &Condvar) -> *mut libc::pthread_cond_t {
24     c.inner.0.get()
25 }
26
27 unsafe impl Send for AllocatedCondvar {}
28 unsafe impl Sync for AllocatedCondvar {}
29
30 impl LazyInit for AllocatedCondvar {
31     fn init() -> Box<Self> {
32         let condvar = Box::new(AllocatedCondvar(UnsafeCell::new(libc::PTHREAD_COND_INITIALIZER)));
33
34         cfg_if::cfg_if! {
35             if #[cfg(any(
36                 target_os = "macos",
37                 target_os = "ios",
38                 target_os = "watchos",
39                 target_os = "l4re",
40                 target_os = "android",
41                 target_os = "redox"
42             ))] {
43                 // `pthread_condattr_setclock` is unfortunately not supported on these platforms.
44             } else if #[cfg(any(target_os = "espidf", target_os = "horizon"))] {
45                 // NOTE: ESP-IDF's PTHREAD_COND_INITIALIZER support is not released yet
46                 // So on that platform, init() should always be called
47                 // Moreover, that platform does not have pthread_condattr_setclock support,
48                 // hence that initialization should be skipped as well
49                 //
50                 // Similar story for the 3DS (horizon).
51                 let r = unsafe { libc::pthread_cond_init(condvar.0.get(), crate::ptr::null()) };
52                 assert_eq!(r, 0);
53             } else {
54                 use crate::mem::MaybeUninit;
55                 let mut attr = MaybeUninit::<libc::pthread_condattr_t>::uninit();
56                 let r = unsafe { libc::pthread_condattr_init(attr.as_mut_ptr()) };
57                 assert_eq!(r, 0);
58                 let r = unsafe { libc::pthread_condattr_setclock(attr.as_mut_ptr(), libc::CLOCK_MONOTONIC) };
59                 assert_eq!(r, 0);
60                 let r = unsafe { libc::pthread_cond_init(condvar.0.get(), attr.as_ptr()) };
61                 assert_eq!(r, 0);
62                 let r = unsafe { libc::pthread_condattr_destroy(attr.as_mut_ptr()) };
63                 assert_eq!(r, 0);
64             }
65         }
66
67         condvar
68     }
69 }
70
71 impl Drop for AllocatedCondvar {
72     #[inline]
73     fn drop(&mut self) {
74         let r = unsafe { libc::pthread_cond_destroy(self.0.get()) };
75         if cfg!(target_os = "dragonfly") {
76             // On DragonFly pthread_cond_destroy() returns EINVAL if called on
77             // a condvar that was just initialized with
78             // libc::PTHREAD_COND_INITIALIZER. Once it is used or
79             // pthread_cond_init() is called, this behaviour no longer occurs.
80             debug_assert!(r == 0 || r == libc::EINVAL);
81         } else {
82             debug_assert_eq!(r, 0);
83         }
84     }
85 }
86
87 impl Condvar {
88     pub const fn new() -> Condvar {
89         Condvar { inner: LazyBox::new(), mutex: AtomicPtr::new(ptr::null_mut()) }
90     }
91
92     #[inline]
93     fn verify(&self, mutex: *mut libc::pthread_mutex_t) {
94         // Relaxed is okay here because we never read through `self.addr`, and only use it to
95         // compare addresses.
96         match self.mutex.compare_exchange(ptr::null_mut(), mutex, Relaxed, Relaxed) {
97             Ok(_) => {}                // Stored the address
98             Err(n) if n == mutex => {} // Lost a race to store the same address
99             _ => panic!("attempted to use a condition variable with two mutexes"),
100         }
101     }
102
103     #[inline]
104     pub fn notify_one(&self) {
105         let r = unsafe { libc::pthread_cond_signal(raw(self)) };
106         debug_assert_eq!(r, 0);
107     }
108
109     #[inline]
110     pub fn notify_all(&self) {
111         let r = unsafe { libc::pthread_cond_broadcast(raw(self)) };
112         debug_assert_eq!(r, 0);
113     }
114
115     #[inline]
116     pub unsafe fn wait(&self, mutex: &Mutex) {
117         let mutex = pthread_mutex::raw(mutex);
118         self.verify(mutex);
119         let r = libc::pthread_cond_wait(raw(self), mutex);
120         debug_assert_eq!(r, 0);
121     }
122
123     // This implementation is used on systems that support pthread_condattr_setclock
124     // where we configure condition variable to use monotonic clock (instead of
125     // default system clock). This approach avoids all problems that result
126     // from changes made to the system time.
127     #[cfg(not(any(
128         target_os = "macos",
129         target_os = "ios",
130         target_os = "watchos",
131         target_os = "android",
132         target_os = "espidf",
133         target_os = "horizon"
134     )))]
135     pub unsafe fn wait_timeout(&self, mutex: &Mutex, dur: Duration) -> bool {
136         use crate::mem;
137
138         let mutex = pthread_mutex::raw(mutex);
139         self.verify(mutex);
140
141         let mut now: libc::timespec = mem::zeroed();
142         let r = libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut now);
143         assert_eq!(r, 0);
144
145         // Nanosecond calculations can't overflow because both values are below 1e9.
146         let nsec = dur.subsec_nanos() + now.tv_nsec as u32;
147
148         let sec = saturating_cast_to_time_t(dur.as_secs())
149             .checked_add((nsec / 1_000_000_000) as libc::time_t)
150             .and_then(|s| s.checked_add(now.tv_sec));
151         let nsec = nsec % 1_000_000_000;
152
153         let timeout =
154             sec.map(|s| libc::timespec { tv_sec: s, tv_nsec: nsec as _ }).unwrap_or(TIMESPEC_MAX);
155
156         let r = libc::pthread_cond_timedwait(raw(self), mutex, &timeout);
157         assert!(r == libc::ETIMEDOUT || r == 0);
158         r == 0
159     }
160
161     // This implementation is modeled after libcxx's condition_variable
162     // https://github.com/llvm-mirror/libcxx/blob/release_35/src/condition_variable.cpp#L46
163     // https://github.com/llvm-mirror/libcxx/blob/release_35/include/__mutex_base#L367
164     #[cfg(any(
165         target_os = "macos",
166         target_os = "ios",
167         target_os = "watchos",
168         target_os = "android",
169         target_os = "espidf",
170         target_os = "horizon"
171     ))]
172     pub unsafe fn wait_timeout(&self, mutex: &Mutex, mut dur: Duration) -> bool {
173         use crate::time::Instant;
174
175         let mutex = pthread_mutex::raw(mutex);
176         self.verify(mutex);
177
178         // 1000 years
179         let max_dur = Duration::from_secs(1000 * 365 * 86400);
180
181         if dur > max_dur {
182             // OSX implementation of `pthread_cond_timedwait` is buggy
183             // with super long durations. When duration is greater than
184             // 0x100_0000_0000_0000 seconds, `pthread_cond_timedwait`
185             // in macOS Sierra return error 316.
186             //
187             // This program demonstrates the issue:
188             // https://gist.github.com/stepancheg/198db4623a20aad2ad7cddb8fda4a63c
189             //
190             // To work around this issue, and possible bugs of other OSes, timeout
191             // is clamped to 1000 years, which is allowable per the API of `wait_timeout`
192             // because of spurious wakeups.
193
194             dur = max_dur;
195         }
196
197         // First, figure out what time it currently is, in both system and
198         // stable time.  pthread_cond_timedwait uses system time, but we want to
199         // report timeout based on stable time.
200         let mut sys_now = libc::timeval { tv_sec: 0, tv_usec: 0 };
201         let stable_now = Instant::now();
202         let r = libc::gettimeofday(&mut sys_now, ptr::null_mut());
203         assert_eq!(r, 0, "unexpected error: {:?}", crate::io::Error::last_os_error());
204
205         let nsec = dur.subsec_nanos() as libc::c_long + (sys_now.tv_usec * 1000) as libc::c_long;
206         let extra = (nsec / 1_000_000_000) as libc::time_t;
207         let nsec = nsec % 1_000_000_000;
208         let seconds = saturating_cast_to_time_t(dur.as_secs());
209
210         let timeout = sys_now
211             .tv_sec
212             .checked_add(extra)
213             .and_then(|s| s.checked_add(seconds))
214             .map(|s| libc::timespec { tv_sec: s, tv_nsec: nsec })
215             .unwrap_or(TIMESPEC_MAX);
216
217         // And wait!
218         let r = libc::pthread_cond_timedwait(raw(self), mutex, &timeout);
219         debug_assert!(r == libc::ETIMEDOUT || r == 0);
220
221         // ETIMEDOUT is not a totally reliable method of determining timeout due
222         // to clock shifts, so do the check ourselves
223         stable_now.elapsed() < dur
224     }
225 }