Skip to main content

relibc/sync/
mod.rs

1//! Synchronization primitives.
2
3pub mod barrier;
4pub mod cond;
5// TODO: Merge with pthread_mutex
6pub mod mutex;
7
8pub mod once;
9pub mod pthread_mutex;
10pub mod rwlock;
11pub mod semaphore;
12pub mod waitval;
13
14pub use self::{
15    mutex::{Mutex, MutexGuard},
16    once::Once,
17    semaphore::Semaphore,
18};
19
20use crate::{
21    error::Errno,
22    header::{
23        errno::{EAGAIN, EINTR, ETIMEDOUT},
24        time::timespec,
25    },
26    out::Out,
27    platform::{Pal, Sys, types::c_int},
28};
29use core::{
30    hint,
31    mem::MaybeUninit,
32    ops::Deref,
33    ptr,
34    sync::atomic::{AtomicI32, AtomicI32 as AtomicInt, AtomicU32},
35};
36
37const FUTEX_WAIT: c_int = 0;
38const FUTEX_WAKE: c_int = 1;
39
40#[derive(Clone, Copy, PartialEq, Eq)]
41pub enum AttemptStatus {
42    Desired,
43    Waiting,
44    Other,
45}
46
47pub trait FutexTy {
48    fn conv(self) -> u32;
49}
50pub trait FutexAtomicTy {
51    type Ty: FutexTy;
52
53    fn ptr(&self) -> *mut Self::Ty;
54}
55impl FutexTy for u32 {
56    fn conv(self) -> u32 {
57        self
58    }
59}
60impl FutexTy for i32 {
61    fn conv(self) -> u32 {
62        self as u32
63    }
64}
65impl FutexAtomicTy for AtomicU32 {
66    type Ty = u32;
67
68    fn ptr(&self) -> *mut u32 {
69        // TODO: Change when Redox's toolchain is updated. This is not about targets, but compiler
70        // versions!
71        /*
72
73        #[cfg(target_os = "redox")]
74        return AtomicU32::as_ptr(self);
75
76        #[cfg(target_os = "linux")]
77        return AtomicU32::as_mut_ptr(self);
78
79        */
80
81        // AtomicU32::as_mut_ptr internally calls UnsafeCell::get, which itself simply does (&self
82        // as *const Self as *mut Self).
83        ptr::from_ref::<AtomicU32>(self) as *mut u32
84    }
85}
86impl FutexAtomicTy for AtomicI32 {
87    type Ty = i32;
88
89    fn ptr(&self) -> *mut i32 {
90        // TODO
91        /*#[cfg(target_os = "redox")]
92        return AtomicI32::as_ptr(self);
93
94        #[cfg(target_os = "linux")]
95        return AtomicI32::as_mut_ptr(self);*/
96
97        ptr::from_ref::<AtomicI32>(self) as *mut i32
98    }
99}
100
101pub unsafe fn futex_wake_ptr(ptr: *mut impl FutexTy, n: i32) -> usize {
102    // TODO: unwrap_unchecked?
103    unsafe { Sys::futex_wake(ptr.cast(), n as u32) }.unwrap() as usize
104}
105pub unsafe fn futex_wait_ptr<T: FutexTy>(
106    ptr: *mut T,
107    value: T,
108    deadline_opt: Option<&timespec>,
109) -> FutexWaitResult {
110    match unsafe { Sys::futex_wait(ptr.cast(), value.conv(), deadline_opt) } {
111        Ok(()) | Err(Errno(EINTR)) => FutexWaitResult::Waited,
112        Err(Errno(EAGAIN)) => FutexWaitResult::Stale,
113        Err(Errno(ETIMEDOUT)) if deadline_opt.is_some() => FutexWaitResult::TimedOut,
114        Err(err) => {
115            todo_error!(0, err, "futex failed");
116            FutexWaitResult::Waited
117        }
118    }
119}
120pub fn futex_wake(atomic: &impl FutexAtomicTy, n: i32) -> usize {
121    unsafe { futex_wake_ptr(atomic.ptr(), n) }
122}
123pub fn futex_wait<T: FutexAtomicTy>(
124    atomic: &T,
125    value: T::Ty,
126    deadline_opt: Option<&timespec>,
127) -> FutexWaitResult {
128    unsafe { futex_wait_ptr(atomic.ptr(), value, deadline_opt) }
129}
130
131#[derive(Clone, Copy, Debug, Eq, PartialEq)]
132pub enum FutexWaitResult {
133    Waited, // possibly spurious
134    Stale,  // outdated value
135    TimedOut,
136}
137
138pub fn rttime() -> timespec {
139    unsafe {
140        let mut time = MaybeUninit::uninit();
141
142        if let Ok(()) = Sys::clock_gettime(
143            crate::header::time::CLOCK_REALTIME,
144            Out::from_uninit_mut(&mut time),
145        ) {}; // TODO handle error
146
147        time.assume_init()
148    }
149}
150
151pub fn wait_until_generic<F1, F2>(word: &AtomicInt, attempt: F1, mark_long: F2, long: c_int)
152where
153    F1: Fn(&AtomicInt) -> AttemptStatus,
154    F2: Fn(&AtomicInt) -> AttemptStatus,
155{
156    // First, try spinning for really short durations
157    for _ in 0..999 {
158        hint::spin_loop();
159        if attempt(word) == AttemptStatus::Desired {
160            return;
161        }
162    }
163
164    // One last attempt, to initiate "previous"
165    let mut previous = attempt(word);
166
167    // Ok, that seems to take quite some time. Let's go into a
168    // longer, more patient, wait.
169    loop {
170        if previous == AttemptStatus::Desired {
171            return;
172        }
173
174        if
175        // If we or somebody else already initiated a long
176        // wait, OR
177        previous == AttemptStatus::Waiting ||
178            // Otherwise, unless our attempt to initiate a long
179            // wait informed us that we might be done waiting
180            mark_long(word) != AttemptStatus::Desired
181        {
182            futex_wait(word, long, None);
183        }
184
185        previous = attempt(word);
186    }
187}
188
189/// Convenient wrapper around the "futex" system call for
190/// synchronization implementations
191#[repr(C)]
192pub(crate) struct AtomicLock {
193    pub(crate) atomic: AtomicInt,
194}
195impl AtomicLock {
196    pub const fn new(value: c_int) -> Self {
197        Self {
198            atomic: AtomicInt::new(value),
199        }
200    }
201    pub fn notify_one(&self) {
202        futex_wake(&self.atomic, 1);
203    }
204    pub fn notify_all(&self) {
205        futex_wake(&self.atomic, i32::MAX);
206    }
207    pub fn wait_if(&self, value: c_int, timeout_opt: Option<&timespec>) {
208        self.wait_if_raw(value, timeout_opt);
209    }
210    pub fn wait_if_raw(&self, value: c_int, timeout_opt: Option<&timespec>) -> FutexWaitResult {
211        futex_wait(&self.atomic, value, timeout_opt)
212    }
213
214    /// A general way to efficiently wait for what might be a long time, using two closures:
215    ///
216    /// - `attempt` = Attempt to modify the atomic value to any
217    ///   desired state.
218    /// - `mark_long` = Attempt to modify the atomic value to sign
219    ///   that it want's to get notified when waiting is done.
220    ///
221    /// Both of these closures are allowed to spuriously give a
222    /// non-success return value, they are used only as optimization
223    /// hints. However, what counts as a "desired value" may differ
224    /// per closure. Therefore, `mark_long` can notify a value as
225    /// "desired" in order to get `attempt` retried immediately.
226    ///
227    /// The `long` parameter is the only one which actually cares
228    /// about the specific value of your atomics. This is needed
229    /// because it needs to pass this to the futex system call in
230    /// order to avoid race conditions where the atomic could be
231    /// modified to the desired value before the call is complete and
232    /// we receive the wakeup notification.
233    pub fn wait_until<F1, F2>(&self, attempt: F1, mark_long: F2, long: c_int)
234    where
235        F1: Fn(&AtomicInt) -> AttemptStatus,
236        F2: Fn(&AtomicInt) -> AttemptStatus,
237    {
238        wait_until_generic(&self.atomic, attempt, mark_long, long)
239    }
240}
241impl Deref for AtomicLock {
242    type Target = AtomicInt;
243
244    fn deref(&self) -> &Self::Target {
245        &self.atomic
246    }
247}