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