118 lines
3.5 KiB
Rust
118 lines
3.5 KiB
Rust
pub mod mutex;
|
|
pub mod once;
|
|
|
|
pub use self::{
|
|
mutex::{Mutex, MutexGuard},
|
|
once::Once,
|
|
};
|
|
|
|
use crate::platform::{types::*, Pal, Sys};
|
|
use core::{
|
|
cell::UnsafeCell,
|
|
ops::Deref,
|
|
sync::atomic::{self, AtomicI32 as AtomicInt},
|
|
};
|
|
|
|
const FUTEX_WAIT: c_int = 0;
|
|
const FUTEX_WAKE: c_int = 1;
|
|
|
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
enum AttemptStatus {
|
|
Desired,
|
|
Waiting,
|
|
Other,
|
|
}
|
|
|
|
/// Convenient wrapper around the "futex" system call for
|
|
/// synchronization implementations
|
|
struct AtomicLock {
|
|
atomic: UnsafeCell<AtomicInt>,
|
|
}
|
|
impl AtomicLock {
|
|
pub const fn new(value: c_int) -> Self {
|
|
Self {
|
|
atomic: UnsafeCell::new(AtomicInt::new(value)),
|
|
}
|
|
}
|
|
pub fn notify_one(&self) {
|
|
Sys::futex(unsafe { &mut *self.atomic.get() }.get_mut(), FUTEX_WAKE, 1);
|
|
}
|
|
pub fn notify_all(&self) {
|
|
Sys::futex(
|
|
unsafe { &mut *self.atomic.get() }.get_mut(),
|
|
FUTEX_WAKE,
|
|
c_int::max_value(),
|
|
);
|
|
}
|
|
pub fn wait_if(&self, value: c_int) {
|
|
Sys::futex(
|
|
unsafe { &mut *self.atomic.get() }.get_mut(),
|
|
FUTEX_WAIT,
|
|
value,
|
|
);
|
|
}
|
|
/// A general way to efficiently wait for what might be a long time, using two closures:
|
|
///
|
|
/// - `attempt` = Attempt to modify the atomic value to any
|
|
/// desired state.
|
|
/// - `mark_long` = Attempt to modify the atomic value to sign
|
|
/// that it want's to get notified when waiting is done.
|
|
///
|
|
/// Both of these closures are allowed to spuriously give a
|
|
/// non-success return value, they are used only as optimization
|
|
/// hints. However, what counts as a "desired value" may differ
|
|
/// per closure. Therefore, `mark_long` can notify a value as
|
|
/// "desired" in order to get `attempt` retried immediately.
|
|
///
|
|
/// The `long` parameter is the only one which actually cares
|
|
/// about the specific value of your atomics. This is needed
|
|
/// because it needs to pass this to the futex system call in
|
|
/// order to avoid race conditions where the atomic could be
|
|
/// modified to the desired value before the call is complete and
|
|
/// we receive the wakeup notification.
|
|
pub fn wait_until<F1, F2>(&self, attempt: F1, mark_long: F2, long: c_int)
|
|
where
|
|
F1: Fn(&AtomicInt) -> AttemptStatus,
|
|
F2: Fn(&AtomicInt) -> AttemptStatus,
|
|
{
|
|
// First, try spinning for really short durations
|
|
for _ in 0..999 {
|
|
atomic::spin_loop_hint();
|
|
if attempt(self) == AttemptStatus::Desired {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// One last attempt, to initiate "previous"
|
|
let mut previous = attempt(self);
|
|
|
|
// Ok, that seems to take quite some time. Let's go into a
|
|
// longer, more patient, wait.
|
|
loop {
|
|
if previous == AttemptStatus::Desired {
|
|
return;
|
|
}
|
|
|
|
if
|
|
// If we or somebody else already initiated a long
|
|
// wait, OR
|
|
previous == AttemptStatus::Waiting ||
|
|
// Otherwise, unless our attempt to initiate a long
|
|
// wait informed us that we might be done waiting
|
|
mark_long(self) != AttemptStatus::Desired
|
|
{
|
|
self.wait_if(long);
|
|
}
|
|
|
|
previous = attempt(self);
|
|
}
|
|
}
|
|
}
|
|
impl Deref for AtomicLock {
|
|
type Target = AtomicInt;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
unsafe { &*self.atomic.get() }
|
|
}
|
|
}
|