0.3.0: converge relibc to upstream 0.6.0 + Red Bear patches
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
use core::{
|
||||
num::NonZeroU32,
|
||||
sync::atomic::{AtomicU32, Ordering},
|
||||
};
|
||||
|
||||
pub struct Barrier {
|
||||
original_count: NonZeroU32,
|
||||
// 4
|
||||
lock: crate::sync::Mutex<Inner>,
|
||||
// 16
|
||||
cvar: FutexState,
|
||||
// 24
|
||||
}
|
||||
#[derive(Debug)]
|
||||
struct Inner {
|
||||
_unused0: u32,
|
||||
_unused1: u32,
|
||||
}
|
||||
|
||||
struct FutexState {
|
||||
count: AtomicU32,
|
||||
sense: AtomicU32,
|
||||
}
|
||||
|
||||
impl FutexState {
|
||||
const fn new(count: u32) -> Self {
|
||||
Self {
|
||||
count: AtomicU32::new(count),
|
||||
sense: AtomicU32::new(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum WaitResult {
|
||||
Waited,
|
||||
NotifiedAll,
|
||||
}
|
||||
|
||||
impl Barrier {
|
||||
pub fn new(count: NonZeroU32) -> Self {
|
||||
Self {
|
||||
original_count: count,
|
||||
lock: crate::sync::Mutex::new(Inner {
|
||||
_unused0: 0,
|
||||
_unused1: 0,
|
||||
}),
|
||||
cvar: FutexState::new(count.get()),
|
||||
}
|
||||
}
|
||||
pub fn wait(&self) -> WaitResult {
|
||||
let _ = &self.lock;
|
||||
let sense = self.cvar.sense.load(Ordering::Acquire);
|
||||
|
||||
if self.cvar.count.fetch_sub(1, Ordering::AcqRel) == 1 {
|
||||
self.cvar
|
||||
.count
|
||||
.store(self.original_count.get(), Ordering::Relaxed);
|
||||
self.cvar
|
||||
.sense
|
||||
.store(sense.wrapping_add(1), Ordering::Release);
|
||||
crate::sync::futex_wake(&self.cvar.sense, i32::MAX);
|
||||
|
||||
WaitResult::NotifiedAll
|
||||
} else {
|
||||
// SMP fix: wait directly on the barrier generation word instead of routing through the
|
||||
// condvar unlock->futex_wait path. If the last thread flips `sense` after we load it
|
||||
// but before our futex wait starts, the futex observes a stale value and returns
|
||||
// immediately instead of sleeping forever after a missed broadcast wakeup.
|
||||
while self.cvar.sense.load(Ordering::Acquire) == sense {
|
||||
let _ = crate::sync::futex_wait(&self.cvar.sense, sense, None);
|
||||
}
|
||||
|
||||
WaitResult::Waited
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
use core::cmp;
|
||||
use core::num::NonZeroU32;
|
||||
use core::sync::atomic::{AtomicU32 as AtomicUint, Ordering};
|
||||
|
||||
pub struct Barrier {
|
||||
waited_count: AtomicUint,
|
||||
notified_count: AtomicUint,
|
||||
cycles_count: AtomicUint,
|
||||
original_count: NonZeroU32,
|
||||
}
|
||||
|
||||
pub enum WaitResult {
|
||||
Waited,
|
||||
NotifiedAll,
|
||||
}
|
||||
|
||||
impl Barrier {
|
||||
pub fn new(count: NonZeroU32) -> Self {
|
||||
Self {
|
||||
waited_count: AtomicUint::new(0),
|
||||
notified_count: AtomicUint::new(0),
|
||||
cycles_count: AtomicUint::new(0),
|
||||
original_count: count,
|
||||
}
|
||||
}
|
||||
pub fn wait(&self) -> WaitResult {
|
||||
// The barrier wait operation can be divided into two parts: (1) incrementing the wait count where
|
||||
// N-1 waiters wait and one notifies the rest, and (2) notifying all threads that have been
|
||||
// waiting.
|
||||
let original_count = self.original_count.get();
|
||||
let mut new = self.waited_count.fetch_add(1, Ordering::Acquire) + 1;
|
||||
let original_cycle_count = self.cycles_count.load(Ordering::Acquire);
|
||||
|
||||
loop {
|
||||
let result = match Ord::cmp(&new, &original_count) {
|
||||
cmp::Ordering::Less => {
|
||||
// new < original_count, i.e. we were one of the threads that incremented the
|
||||
// counter, and will return without SERIAL_THREAD later, but need to continue
|
||||
// waiting for the last waiter to notify the others.
|
||||
|
||||
loop {
|
||||
let count = self.waited_count.load(Ordering::Acquire);
|
||||
|
||||
if count >= original_count { break }
|
||||
|
||||
let _ = crate::sync::futex_wait(&self.waited_count, count, None);
|
||||
}
|
||||
|
||||
WaitResult::Waited
|
||||
}
|
||||
cmp::Ordering::Equal => {
|
||||
// new == original_count, i.e. we were the one thread doing the last increment, and we
|
||||
// will be responsible for waking up all other waiters.
|
||||
|
||||
crate::sync::futex_wake(&self.waited_count, original_count as i32 - 1);
|
||||
|
||||
WaitResult::NotifiedAll
|
||||
}
|
||||
cmp::Ordering::Greater => {
|
||||
let mut next_cycle_count;
|
||||
|
||||
loop {
|
||||
next_cycle_count = self.cycles_count.load(Ordering::Acquire);
|
||||
|
||||
if next_cycle_count != original_cycle_count {
|
||||
break;
|
||||
}
|
||||
|
||||
crate::sync::futex_wait(&self.cycles_count, next_cycle_count, None);
|
||||
}
|
||||
let difference = next_cycle_count.wrapping_sub(original_cycle_count);
|
||||
|
||||
new = new.saturating_sub(difference * original_cycle_count);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if self.notified_count.fetch_add(1, Ordering::AcqRel) + 1 == original_count {
|
||||
self.notified_count.store(0, Ordering::Relaxed);
|
||||
// Cycle count can be incremented nonatomically here, as this branch can only be
|
||||
// reached once until waited_count is decremented again.
|
||||
self.cycles_count.store(self.cycles_count.load(Ordering::Acquire).wrapping_add(1), Ordering::Release);
|
||||
|
||||
let _ = self.waited_count.fetch_sub(original_count, Ordering::Relaxed);
|
||||
|
||||
let _ = crate::sync::futex_wake(&self.cycles_count, i32::MAX);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
// Used design from https://www.remlab.net/op/futex-condvar.shtml
|
||||
|
||||
use crate::{
|
||||
error::Errno,
|
||||
header::{
|
||||
bits_timespec::timespec,
|
||||
errno::{EINVAL, ETIMEDOUT},
|
||||
pthread::*,
|
||||
time::{CLOCK_MONOTONIC, CLOCK_REALTIME, timespec_realtime_to_monotonic},
|
||||
},
|
||||
platform::types::clockid_t,
|
||||
};
|
||||
|
||||
use core::sync::atomic::{AtomicU32 as AtomicUint, Ordering};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct CondAttr {
|
||||
pub clock: clockid_t,
|
||||
pub pshared: i32,
|
||||
}
|
||||
|
||||
impl Default for CondAttr {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
// defaults according to POSIX
|
||||
clock: CLOCK_REALTIME, // for timedwait
|
||||
pshared: PTHREAD_PROCESS_PRIVATE, // TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Cond {
|
||||
cur: AtomicUint,
|
||||
prev: AtomicUint,
|
||||
}
|
||||
|
||||
type Result<T, E = Errno> = core::result::Result<T, E>;
|
||||
|
||||
impl Default for Cond {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Cond {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
cur: AtomicUint::new(0),
|
||||
prev: AtomicUint::new(0),
|
||||
}
|
||||
}
|
||||
fn wake(&self, count: i32) -> Result<(), Errno> {
|
||||
// This is formally correct as long as we don't have more than u32::MAX threads.
|
||||
let prev = self.prev.load(Ordering::Relaxed);
|
||||
self.cur.store(prev.wrapping_add(1), Ordering::Relaxed);
|
||||
|
||||
crate::sync::futex_wake(&self.cur, count);
|
||||
Ok(())
|
||||
}
|
||||
pub fn broadcast(&self) -> Result<(), Errno> {
|
||||
self.wake(i32::MAX)
|
||||
}
|
||||
pub fn signal(&self) -> Result<(), Errno> {
|
||||
// POSIX requires pthread_cond_signal to wake AT LEAST ONE waiter that
|
||||
// is currently waiting on the condition variable, but it must not
|
||||
// wake all waiters (that is pthread_cond_broadcast semantics).
|
||||
// Wake exactly one via FUTEX_WAKE with count=1. Using broadcast() here
|
||||
// was a thundering-herd bug: every cond_signal woke every waiter on
|
||||
// every CPU. Fixed 2026-07-02 (Red Bear OS multi-threading plan,
|
||||
// Phase 0a).
|
||||
self.wake(1)
|
||||
}
|
||||
pub fn clockwait(
|
||||
&self,
|
||||
mutex: &RlctMutex,
|
||||
timeout: ×pec,
|
||||
clock_id: clockid_t,
|
||||
) -> Result<(), Errno> {
|
||||
let relative = match clock_id {
|
||||
// FUTEX expect monotonic clock
|
||||
CLOCK_MONOTONIC => timeout.clone(),
|
||||
CLOCK_REALTIME => timespec_realtime_to_monotonic(timeout.clone())?,
|
||||
_ => return Err(Errno(EINVAL)),
|
||||
};
|
||||
|
||||
self.wait_inner(mutex, Some(&relative))
|
||||
}
|
||||
pub fn timedwait(&self, mutex: &RlctMutex, timeout: ×pec) -> Result<(), Errno> {
|
||||
// TODO: The clock can be other than CLOCK_REALTIME depends on CondAttr
|
||||
self.clockwait(mutex, timeout, CLOCK_REALTIME)
|
||||
}
|
||||
fn wait_inner(&self, mutex: &RlctMutex, timeout: Option<×pec>) -> Result<(), Errno> {
|
||||
self.wait_inner_generic(|| mutex.unlock(), || mutex.lock(), timeout)
|
||||
}
|
||||
pub fn wait_inner_typedmutex<'lock, T>(
|
||||
&self,
|
||||
guard: crate::sync::MutexGuard<'lock, T>,
|
||||
) -> crate::sync::MutexGuard<'lock, T> {
|
||||
let mut newguard = None;
|
||||
let lock = guard.mutex;
|
||||
self.wait_inner_generic(
|
||||
move || {
|
||||
drop(guard);
|
||||
Ok(())
|
||||
},
|
||||
|| {
|
||||
newguard = Some(lock.lock());
|
||||
Ok(())
|
||||
},
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
newguard.unwrap()
|
||||
}
|
||||
// TODO: FUTEX_REQUEUE
|
||||
fn wait_inner_generic(
|
||||
&self,
|
||||
unlock: impl FnOnce() -> Result<()>,
|
||||
lock: impl FnOnce() -> Result<()>,
|
||||
deadline: Option<×pec>,
|
||||
) -> Result<(), Errno> {
|
||||
// TODO: Error checking for certain types (i.e. robust and errorcheck) of mutexes, e.g. if the
|
||||
// mutex is not locked.
|
||||
let current = self.cur.load(Ordering::Relaxed);
|
||||
self.prev.store(current, Ordering::Relaxed);
|
||||
|
||||
unlock()?;
|
||||
let futex_r = crate::sync::futex_wait(&self.cur, current, deadline);
|
||||
lock()?;
|
||||
|
||||
match futex_r {
|
||||
super::FutexWaitResult::Waited => Ok(()),
|
||||
super::FutexWaitResult::Stale => Ok(()),
|
||||
super::FutexWaitResult::TimedOut => Err(Errno(ETIMEDOUT)),
|
||||
}
|
||||
}
|
||||
pub fn wait(&self, mutex: &RlctMutex) -> Result<(), Errno> {
|
||||
self.wait_inner(mutex, None)
|
||||
}
|
||||
}
|
||||
+181
-53
@@ -1,6 +1,15 @@
|
||||
//! Synchronization primitives.
|
||||
|
||||
pub mod barrier;
|
||||
pub mod cond;
|
||||
// TODO: Merge with pthread_mutex
|
||||
pub mod mutex;
|
||||
|
||||
pub mod once;
|
||||
pub mod pthread_mutex;
|
||||
pub mod rwlock;
|
||||
pub mod semaphore;
|
||||
pub mod waitval;
|
||||
|
||||
pub use self::{
|
||||
mutex::{Mutex, MutexGuard},
|
||||
@@ -8,57 +17,206 @@ pub use self::{
|
||||
semaphore::Semaphore,
|
||||
};
|
||||
|
||||
use crate::platform::{types::*, Pal, Sys};
|
||||
use crate::{
|
||||
error::Errno,
|
||||
header::{
|
||||
bits_timespec::timespec,
|
||||
errno::{EAGAIN, EINTR, ETIMEDOUT},
|
||||
},
|
||||
out::Out,
|
||||
platform::{Pal, Sys, types::c_int},
|
||||
};
|
||||
use core::{
|
||||
cell::UnsafeCell,
|
||||
hint,
|
||||
mem::MaybeUninit,
|
||||
ops::Deref,
|
||||
sync::atomic::{self, AtomicI32 as AtomicInt},
|
||||
ptr,
|
||||
sync::atomic::{AtomicI32, AtomicI32 as AtomicInt, AtomicU32},
|
||||
};
|
||||
|
||||
const FUTEX_WAIT: c_int = 0;
|
||||
const FUTEX_WAKE: c_int = 1;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum AttemptStatus {
|
||||
pub enum AttemptStatus {
|
||||
Desired,
|
||||
Waiting,
|
||||
Other,
|
||||
}
|
||||
|
||||
pub trait FutexTy {
|
||||
fn conv(self) -> u32;
|
||||
}
|
||||
pub trait FutexAtomicTy {
|
||||
type Ty: FutexTy;
|
||||
|
||||
fn ptr(&self) -> *mut Self::Ty;
|
||||
}
|
||||
impl FutexTy for u32 {
|
||||
fn conv(self) -> u32 {
|
||||
self
|
||||
}
|
||||
}
|
||||
impl FutexTy for i32 {
|
||||
fn conv(self) -> u32 {
|
||||
self as u32
|
||||
}
|
||||
}
|
||||
impl FutexAtomicTy for AtomicU32 {
|
||||
type Ty = u32;
|
||||
|
||||
fn ptr(&self) -> *mut u32 {
|
||||
// TODO: Change when Redox's toolchain is updated. This is not about targets, but compiler
|
||||
// versions!
|
||||
/*
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
return AtomicU32::as_ptr(self);
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
return AtomicU32::as_mut_ptr(self);
|
||||
|
||||
*/
|
||||
|
||||
// AtomicU32::as_mut_ptr internally calls UnsafeCell::get, which itself simply does (&self
|
||||
// as *const Self as *mut Self).
|
||||
ptr::from_ref::<AtomicU32>(self) as *mut u32
|
||||
}
|
||||
}
|
||||
impl FutexAtomicTy for AtomicI32 {
|
||||
type Ty = i32;
|
||||
|
||||
fn ptr(&self) -> *mut i32 {
|
||||
// TODO
|
||||
/*#[cfg(target_os = "redox")]
|
||||
return AtomicI32::as_ptr(self);
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
return AtomicI32::as_mut_ptr(self);*/
|
||||
|
||||
ptr::from_ref::<AtomicI32>(self) as *mut i32
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn futex_wake_ptr(ptr: *mut impl FutexTy, n: i32) -> usize {
|
||||
// TODO: unwrap_unchecked?
|
||||
unsafe { Sys::futex_wake(ptr.cast(), n as u32) }.unwrap() as usize
|
||||
}
|
||||
pub unsafe fn futex_wait_ptr<T: FutexTy>(
|
||||
ptr: *mut T,
|
||||
value: T,
|
||||
deadline_opt: Option<×pec>,
|
||||
) -> FutexWaitResult {
|
||||
match unsafe { Sys::futex_wait(ptr.cast(), value.conv(), deadline_opt) } {
|
||||
Ok(()) | Err(Errno(EINTR)) => FutexWaitResult::Waited,
|
||||
Err(Errno(EAGAIN)) => FutexWaitResult::Stale,
|
||||
Err(Errno(ETIMEDOUT)) if deadline_opt.is_some() => FutexWaitResult::TimedOut,
|
||||
Err(err) => {
|
||||
todo_error!(0, err, "futex failed");
|
||||
FutexWaitResult::Waited
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn futex_wake(atomic: &impl FutexAtomicTy, n: i32) -> usize {
|
||||
unsafe { futex_wake_ptr(atomic.ptr(), n) }
|
||||
}
|
||||
pub fn futex_wait<T: FutexAtomicTy>(
|
||||
atomic: &T,
|
||||
value: T::Ty,
|
||||
deadline_opt: Option<×pec>,
|
||||
) -> FutexWaitResult {
|
||||
unsafe { futex_wait_ptr(atomic.ptr(), value, deadline_opt) }
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum FutexWaitResult {
|
||||
Waited, // possibly spurious
|
||||
Stale, // outdated value
|
||||
TimedOut,
|
||||
}
|
||||
|
||||
pub fn rttime() -> timespec {
|
||||
unsafe {
|
||||
let mut time = MaybeUninit::uninit();
|
||||
|
||||
if let Ok(()) = Sys::clock_gettime(
|
||||
crate::header::time::CLOCK_REALTIME,
|
||||
Out::from_uninit_mut(&mut time),
|
||||
) {}; // TODO handle error
|
||||
|
||||
time.assume_init()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wait_until_generic<F1, F2>(word: &AtomicInt, 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 {
|
||||
hint::spin_loop();
|
||||
if attempt(word) == AttemptStatus::Desired {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// One last attempt, to initiate "previous"
|
||||
let mut previous = attempt(word);
|
||||
|
||||
// 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(word) != AttemptStatus::Desired
|
||||
{
|
||||
futex_wait(word, long, None);
|
||||
}
|
||||
|
||||
previous = attempt(word);
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenient wrapper around the "futex" system call for
|
||||
/// synchronization implementations
|
||||
struct AtomicLock {
|
||||
atomic: UnsafeCell<AtomicInt>,
|
||||
#[repr(C)]
|
||||
pub(crate) struct AtomicLock {
|
||||
pub(crate) atomic: AtomicInt,
|
||||
}
|
||||
impl AtomicLock {
|
||||
pub const fn new(value: c_int) -> Self {
|
||||
Self {
|
||||
atomic: UnsafeCell::new(AtomicInt::new(value)),
|
||||
atomic: AtomicInt::new(value),
|
||||
}
|
||||
}
|
||||
pub fn notify_one(&self) {
|
||||
Sys::futex(unsafe { &mut *self.atomic.get() }.get_mut(), FUTEX_WAKE, 1);
|
||||
futex_wake(&self.atomic, 1);
|
||||
}
|
||||
pub fn notify_all(&self) {
|
||||
Sys::futex(
|
||||
unsafe { &mut *self.atomic.get() }.get_mut(),
|
||||
FUTEX_WAKE,
|
||||
c_int::max_value(),
|
||||
);
|
||||
futex_wake(&self.atomic, i32::MAX);
|
||||
}
|
||||
pub fn wait_if(&self, value: c_int) {
|
||||
Sys::futex(
|
||||
unsafe { &mut *self.atomic.get() }.get_mut(),
|
||||
FUTEX_WAIT,
|
||||
value,
|
||||
);
|
||||
pub fn wait_if(&self, value: c_int, timeout_opt: Option<×pec>) {
|
||||
self.wait_if_raw(value, timeout_opt);
|
||||
}
|
||||
pub fn wait_if_raw(&self, value: c_int, timeout_opt: Option<×pec>) -> FutexWaitResult {
|
||||
futex_wait(&self.atomic, value, timeout_opt)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// desired state.
|
||||
/// - `mark_long` = Attempt to modify the atomic value to sign
|
||||
/// that it want's to get notified when waiting is done.
|
||||
/// 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
|
||||
@@ -77,43 +235,13 @@ impl AtomicLock {
|
||||
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);
|
||||
}
|
||||
wait_until_generic(&self.atomic, attempt, mark_long, long)
|
||||
}
|
||||
}
|
||||
impl Deref for AtomicLock {
|
||||
type Target = AtomicInt;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { &*self.atomic.get() }
|
||||
&self.atomic
|
||||
}
|
||||
}
|
||||
|
||||
+55
-36
@@ -1,21 +1,55 @@
|
||||
use super::{AtomicLock, AttemptStatus};
|
||||
use crate::platform::types::*;
|
||||
use crate::platform::types::c_int;
|
||||
use core::{
|
||||
cell::UnsafeCell,
|
||||
ops::{Deref, DerefMut},
|
||||
sync::atomic::Ordering::SeqCst,
|
||||
sync::atomic::{AtomicI32 as AtomicInt, Ordering},
|
||||
};
|
||||
|
||||
const UNLOCKED: c_int = 0;
|
||||
const LOCKED: c_int = 1;
|
||||
const WAITING: c_int = 2;
|
||||
pub(crate) const UNLOCKED: c_int = 0;
|
||||
pub(crate) const LOCKED: c_int = 1;
|
||||
pub(crate) const WAITING: c_int = 2;
|
||||
|
||||
pub struct Mutex<T> {
|
||||
lock: AtomicLock,
|
||||
pub(crate) lock: AtomicLock,
|
||||
content: UnsafeCell<T>,
|
||||
}
|
||||
unsafe impl<T: Send> Send for Mutex<T> {}
|
||||
unsafe impl<T: Send> Sync for Mutex<T> {}
|
||||
|
||||
pub(crate) unsafe fn manual_try_lock_generic(word: &AtomicInt) -> bool {
|
||||
word.compare_exchange(UNLOCKED, LOCKED, Ordering::Acquire, Ordering::Relaxed)
|
||||
.is_ok()
|
||||
}
|
||||
pub(crate) unsafe fn manual_lock_generic(word: &AtomicInt) {
|
||||
crate::sync::wait_until_generic(
|
||||
word,
|
||||
|lock| {
|
||||
lock.compare_exchange_weak(UNLOCKED, LOCKED, Ordering::Acquire, Ordering::Relaxed)
|
||||
.map(|_| AttemptStatus::Desired)
|
||||
.unwrap_or_else(|e| match e {
|
||||
WAITING => AttemptStatus::Waiting,
|
||||
_ => AttemptStatus::Other,
|
||||
})
|
||||
},
|
||||
|lock| match lock
|
||||
// TODO: Ordering
|
||||
.compare_exchange_weak(LOCKED, WAITING, Ordering::SeqCst, Ordering::SeqCst)
|
||||
.unwrap_or_else(|e| e)
|
||||
{
|
||||
UNLOCKED => AttemptStatus::Desired,
|
||||
WAITING => AttemptStatus::Waiting,
|
||||
_ => AttemptStatus::Other,
|
||||
},
|
||||
WAITING,
|
||||
);
|
||||
}
|
||||
pub(crate) unsafe fn manual_unlock_generic(word: &AtomicInt) {
|
||||
if word.swap(UNLOCKED, Ordering::Release) == WAITING {
|
||||
crate::sync::futex_wake(word, i32::MAX);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Mutex<T> {
|
||||
/// Create a new mutex
|
||||
pub const fn new(content: T) -> Self {
|
||||
@@ -42,45 +76,30 @@ impl<T> Mutex<T> {
|
||||
/// on failure. You should probably not worry about this, it's used for
|
||||
/// internal optimizations.
|
||||
pub unsafe fn manual_try_lock(&self) -> Result<&mut T, c_int> {
|
||||
self.lock
|
||||
.compare_exchange(UNLOCKED, LOCKED, SeqCst, SeqCst)
|
||||
.map(|_| &mut *self.content.get())
|
||||
if unsafe { manual_try_lock_generic(&self.lock) } {
|
||||
Ok(unsafe { &mut *self.content.get() })
|
||||
} else {
|
||||
Err(0)
|
||||
}
|
||||
}
|
||||
/// Lock the mutex, returning the inner content. After doing this, it's
|
||||
/// your responsibility to unlock it after usage. Mostly useful for FFI:
|
||||
/// Prefer normal .lock() where possible.
|
||||
pub unsafe fn manual_lock(&self) -> &mut T {
|
||||
self.lock.wait_until(
|
||||
|lock| {
|
||||
lock.compare_exchange_weak(UNLOCKED, LOCKED, SeqCst, SeqCst)
|
||||
.map(|_| AttemptStatus::Desired)
|
||||
.unwrap_or_else(|e| match e {
|
||||
WAITING => AttemptStatus::Waiting,
|
||||
_ => AttemptStatus::Other,
|
||||
})
|
||||
},
|
||||
|lock| match lock
|
||||
.compare_exchange_weak(LOCKED, WAITING, SeqCst, SeqCst)
|
||||
.unwrap_or_else(|e| e)
|
||||
{
|
||||
UNLOCKED => AttemptStatus::Desired,
|
||||
WAITING => AttemptStatus::Waiting,
|
||||
_ => AttemptStatus::Other,
|
||||
},
|
||||
WAITING,
|
||||
);
|
||||
&mut *self.content.get()
|
||||
unsafe { manual_lock_generic(&self.lock) };
|
||||
unsafe { &mut *self.content.get() }
|
||||
}
|
||||
/// Unlock the mutex, if it's locked.
|
||||
pub unsafe fn manual_unlock(&self) {
|
||||
if self.lock.swap(UNLOCKED, SeqCst) == WAITING {
|
||||
self.lock.notify_one();
|
||||
}
|
||||
unsafe { manual_unlock_generic(&self.lock) }
|
||||
}
|
||||
pub fn as_ptr(&self) -> *mut T {
|
||||
self.content.get()
|
||||
}
|
||||
|
||||
/// Tries to lock the mutex and returns a guard that automatically unlocks
|
||||
/// the mutex when it falls out of scope.
|
||||
pub fn try_lock(&self) -> Option<MutexGuard<T>> {
|
||||
pub fn try_lock(&self) -> Option<MutexGuard<'_, T>> {
|
||||
unsafe {
|
||||
self.manual_try_lock().ok().map(|content| MutexGuard {
|
||||
mutex: self,
|
||||
@@ -90,7 +109,7 @@ impl<T> Mutex<T> {
|
||||
}
|
||||
/// Locks the mutex and returns a guard that automatically unlocks the
|
||||
/// mutex when it falls out of scope.
|
||||
pub fn lock(&self) -> MutexGuard<T> {
|
||||
pub fn lock(&self) -> MutexGuard<'_, T> {
|
||||
MutexGuard {
|
||||
mutex: self,
|
||||
content: unsafe { self.manual_lock() },
|
||||
@@ -99,14 +118,14 @@ impl<T> Mutex<T> {
|
||||
}
|
||||
|
||||
pub struct MutexGuard<'a, T: 'a> {
|
||||
mutex: &'a Mutex<T>,
|
||||
pub(crate) mutex: &'a Mutex<T>,
|
||||
content: &'a mut T,
|
||||
}
|
||||
impl<'a, T> Deref for MutexGuard<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.content
|
||||
self.content
|
||||
}
|
||||
}
|
||||
impl<'a, T> DerefMut for MutexGuard<'a, T> {
|
||||
|
||||
+80
-25
@@ -1,6 +1,10 @@
|
||||
use super::{AtomicLock, AttemptStatus};
|
||||
use super::AttemptStatus;
|
||||
use crate::platform::types::*;
|
||||
use core::{cell::UnsafeCell, mem::MaybeUninit, sync::atomic::Ordering::SeqCst};
|
||||
use core::{
|
||||
cell::UnsafeCell,
|
||||
mem::MaybeUninit,
|
||||
sync::atomic::{AtomicI32 as AtomicInt, Ordering},
|
||||
};
|
||||
|
||||
const UNINITIALIZED: c_int = 0;
|
||||
const INITIALIZING: c_int = 1;
|
||||
@@ -8,44 +12,79 @@ const WAITING: c_int = 2;
|
||||
const INITIALIZED: c_int = 3;
|
||||
|
||||
pub struct Once<T> {
|
||||
status: AtomicLock,
|
||||
status: AtomicInt,
|
||||
data: UnsafeCell<MaybeUninit<T>>,
|
||||
}
|
||||
|
||||
// SAFETY:
|
||||
//
|
||||
// Sending a Once is the same as sending a (wrapped) T.
|
||||
unsafe impl<T: Send> Send for Once<T> {}
|
||||
unsafe impl<T: Send> Sync for Once<T> {}
|
||||
|
||||
// SAFETY:
|
||||
//
|
||||
// For Once to be shared between threads without being unsound, only call_once needs to be safe, at
|
||||
// the moment.
|
||||
//
|
||||
// Send requirement: the thread that gets to run the initializer function, will put a T in the cell
|
||||
// which can then be accessed by other threads, thus T needs to be send.
|
||||
//
|
||||
// Sync requirement: after call_once has been called, it returns the value via &T, which naturally
|
||||
// forces T to be Sync.
|
||||
unsafe impl<T: Send + Sync> Sync for Once<T> {}
|
||||
|
||||
impl<T> Once<T> {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
status: AtomicLock::new(UNINITIALIZED),
|
||||
status: AtomicInt::new(UNINITIALIZED),
|
||||
data: UnsafeCell::new(MaybeUninit::uninit()),
|
||||
}
|
||||
}
|
||||
pub fn call_once<F>(&self, f: F) -> &mut T
|
||||
where
|
||||
F: FnOnce() -> T,
|
||||
{
|
||||
match self
|
||||
.status
|
||||
.compare_and_swap(UNINITIALIZED, INITIALIZING, SeqCst)
|
||||
{
|
||||
UNINITIALIZED => {
|
||||
// We now have a lock, let's initiate things!
|
||||
let ret = unsafe { &mut *self.data.get() }.write(f());
|
||||
pub fn call_once(&self, constructor: impl FnOnce() -> T) -> &T {
|
||||
match self.status.compare_exchange(
|
||||
UNINITIALIZED,
|
||||
INITIALIZING,
|
||||
// SAFETY: Success ordering: if the CAS succeeds, we technically need no
|
||||
// synchronization besides the Release store to INITIALIZED, and Acquire here forbids
|
||||
// possible loads in f() to be re-ordered before this CAS. One could argue whether or
|
||||
// not that is reasonable, but the main point is that the success ordering must be at
|
||||
// least as strong as the failure ordering.
|
||||
Ordering::Acquire,
|
||||
// SAFETY: Failure ordering: if the CAS fails, and status was INITIALIZING | WAITING,
|
||||
// then Relaxed is sufficient, as it will have to be Acquire-loaded again later. If
|
||||
// INITIALIZED is encountered however, it will nonatomically read the value in the
|
||||
// Cell, which necessitates Acquire.
|
||||
Ordering::Acquire, // TODO: On archs where this matters, use Relaxed and core::sync::atomic::fence?
|
||||
) {
|
||||
Ok(_must_be_uninit) => {
|
||||
// We now have exclusive access to the cell, let's initiate things!
|
||||
unsafe { self.data.get().cast::<T>().write(constructor()) };
|
||||
|
||||
// Mark the data as initialized
|
||||
if self.status.swap(INITIALIZED, SeqCst) == WAITING {
|
||||
if self.status.swap(INITIALIZED, Ordering::Release) == WAITING {
|
||||
// At least one thread is waiting on this to finish
|
||||
self.status.notify_all();
|
||||
crate::sync::futex_wake(&self.status, i32::MAX);
|
||||
}
|
||||
}
|
||||
INITIALIZING | WAITING => self.status.wait_until(
|
||||
|lock| match lock.load(SeqCst) {
|
||||
Err(INITIALIZING) | Err(WAITING) => crate::sync::wait_until_generic(
|
||||
&self.status,
|
||||
// SAFETY: An Acquire load is necessary for the nonatomic store by the thread
|
||||
// running the constructor, to become visible.
|
||||
|status| match status.load(Ordering::Acquire) {
|
||||
WAITING => AttemptStatus::Waiting,
|
||||
INITIALIZED => AttemptStatus::Desired,
|
||||
_ => AttemptStatus::Other,
|
||||
},
|
||||
|lock| match lock
|
||||
.compare_exchange_weak(INITIALIZING, WAITING, SeqCst, SeqCst)
|
||||
// SAFETY: Double-Acquire is necessary here as well, because if the CAS fails and
|
||||
// it was INITIALIZED, the nonatomic write by the constructor thread, must be
|
||||
// visible.
|
||||
|status| match status
|
||||
.compare_exchange_weak(
|
||||
INITIALIZING,
|
||||
WAITING,
|
||||
Ordering::Acquire,
|
||||
Ordering::Acquire,
|
||||
)
|
||||
.unwrap_or_else(|e| e)
|
||||
{
|
||||
WAITING => AttemptStatus::Waiting,
|
||||
@@ -54,12 +93,14 @@ impl<T> Once<T> {
|
||||
},
|
||||
WAITING,
|
||||
),
|
||||
INITIALIZED => (),
|
||||
_ => unreachable!("invalid state for Once<T>"),
|
||||
Err(INITIALIZED) => (),
|
||||
|
||||
// TODO: Only for debug builds?
|
||||
Err(_) => unreachable!("invalid state for Once<T>"),
|
||||
}
|
||||
|
||||
// At this point the data must be initialized!
|
||||
unsafe { &mut *(&mut *self.data.get()).as_mut_ptr() }
|
||||
unsafe { (&*self.data.get()).assume_init_ref() }
|
||||
}
|
||||
}
|
||||
impl<T> Default for Once<T> {
|
||||
@@ -67,3 +108,17 @@ impl<T> Default for Once<T> {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
// TODO: Drop doesn't work well in const fn, instead use a wrapper for relibc Rust code that adds
|
||||
// Drop, and don't use that wrapper when writing the header file impls.
|
||||
/*
|
||||
impl<T> Drop for Once<T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if *self.status.get_mut() == INITIALIZED {
|
||||
// SAFETY: It must be initialized, because of the above condition.
|
||||
self.data.get_mut().assume_init_drop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,404 @@
|
||||
use alloc::boxed::Box;
|
||||
use core::{
|
||||
cell::Cell,
|
||||
sync::atomic::{AtomicU32 as AtomicUint, Ordering},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::Errno,
|
||||
header::{bits_timespec::timespec, errno::*, pthread::*},
|
||||
platform::{Pal, Sys, types::c_int},
|
||||
};
|
||||
|
||||
use super::FutexWaitResult;
|
||||
|
||||
pub struct RlctMutex {
|
||||
// Actual locking word.
|
||||
inner: AtomicUint,
|
||||
recursive_count: AtomicUint,
|
||||
|
||||
ty: Ty,
|
||||
robust: bool,
|
||||
}
|
||||
|
||||
pub struct RobustMutexNode {
|
||||
pub next: *mut RobustMutexNode,
|
||||
pub prev: *mut RobustMutexNode,
|
||||
pub mutex: *const RlctMutex,
|
||||
}
|
||||
|
||||
const STATE_UNLOCKED: u32 = 0;
|
||||
const WAITING_BIT: u32 = 1 << 31;
|
||||
const FUTEX_OWNER_DIED: u32 = 1 << 30;
|
||||
const INDEX_MASK: u32 = !(WAITING_BIT | FUTEX_OWNER_DIED);
|
||||
|
||||
// TODO: Lower limit is probably better.
|
||||
const RECURSIVE_COUNT_MAX_INCLUSIVE: u32 = u32::MAX;
|
||||
// TODO: How many spins should we do before it becomes more time-economical to enter kernel mode
|
||||
// via futexes?
|
||||
const SPIN_COUNT: usize = 100;
|
||||
|
||||
impl RlctMutex {
|
||||
pub(crate) fn new(attr: &RlctMutexAttr) -> Result<Self, Errno> {
|
||||
let RlctMutexAttr {
|
||||
prioceiling,
|
||||
protocol,
|
||||
pshared: _,
|
||||
robust,
|
||||
ty,
|
||||
} = *attr;
|
||||
|
||||
Ok(Self {
|
||||
inner: AtomicUint::new(STATE_UNLOCKED),
|
||||
recursive_count: AtomicUint::new(0),
|
||||
robust: match robust {
|
||||
PTHREAD_MUTEX_STALLED => false,
|
||||
PTHREAD_MUTEX_ROBUST => true,
|
||||
|
||||
_ => return Err(Errno(EINVAL)),
|
||||
},
|
||||
ty: match ty {
|
||||
PTHREAD_MUTEX_DEFAULT => Ty::Def,
|
||||
PTHREAD_MUTEX_ERRORCHECK => Ty::Errck,
|
||||
PTHREAD_MUTEX_RECURSIVE => Ty::Recursive,
|
||||
PTHREAD_MUTEX_NORMAL => Ty::Normal,
|
||||
|
||||
_ => return Err(Errno(EINVAL)),
|
||||
},
|
||||
})
|
||||
}
|
||||
pub fn prioceiling(&self) -> Result<c_int, Errno> {
|
||||
todo_skip!(0, "pthread_getprioceiling: not implemented");
|
||||
Ok(0)
|
||||
}
|
||||
pub fn replace_prioceiling(&self, _: c_int) -> Result<c_int, Errno> {
|
||||
todo_skip!(0, "pthread_setprioceiling: not implemented");
|
||||
Ok(0)
|
||||
}
|
||||
pub fn make_consistent(&self) -> Result<(), Errno> {
|
||||
debug_assert!(self.robust, "make_consistent called on non-robust mutex");
|
||||
|
||||
if !self.robust {
|
||||
return Err(Errno(EINVAL));
|
||||
}
|
||||
|
||||
let current = self.inner.load(Ordering::Relaxed);
|
||||
let owner = current & INDEX_MASK;
|
||||
|
||||
if owner == os_tid_invalid_after_fork() && current & FUTEX_OWNER_DIED != 0 {
|
||||
self.inner.store(0, Ordering::Release);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Errno(EINVAL))
|
||||
}
|
||||
}
|
||||
fn lock_inner(&self, deadline: Option<×pec>) -> Result<(), Errno> {
|
||||
let this_thread = os_tid_invalid_after_fork();
|
||||
let mut spins_left = SPIN_COUNT;
|
||||
|
||||
loop {
|
||||
let result = self.inner.compare_exchange_weak(
|
||||
STATE_UNLOCKED,
|
||||
this_thread,
|
||||
Ordering::Acquire,
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok(_) => return self.finish_lock_acquire(false),
|
||||
Err(thread) if thread & INDEX_MASK == this_thread && self.ty == Ty::Recursive => {
|
||||
self.increment_recursive_count()?;
|
||||
return Ok(());
|
||||
}
|
||||
Err(thread) if thread & INDEX_MASK == this_thread && self.ty == Ty::Errck => {
|
||||
return Err(Errno(EDEADLK));
|
||||
}
|
||||
Err(thread) if thread & FUTEX_OWNER_DIED != 0 && thread & INDEX_MASK == 0 => {
|
||||
return Err(Errno(ENOTRECOVERABLE));
|
||||
}
|
||||
Err(thread) if thread & FUTEX_OWNER_DIED != 0 => {
|
||||
if !self.robust {
|
||||
return Err(Errno(ENOTRECOVERABLE));
|
||||
}
|
||||
|
||||
let new_value = (thread & WAITING_BIT) | FUTEX_OWNER_DIED | this_thread;
|
||||
match self.inner.compare_exchange(
|
||||
thread,
|
||||
new_value,
|
||||
Ordering::Acquire,
|
||||
Ordering::Relaxed,
|
||||
) {
|
||||
Ok(_) => return self.finish_lock_acquire(true),
|
||||
Err(_) => continue,
|
||||
}
|
||||
}
|
||||
Err(thread) if thread & INDEX_MASK == 0 => continue,
|
||||
Err(thread) => {
|
||||
let owner = thread & INDEX_MASK;
|
||||
|
||||
if self.robust && !crate::pthread::mutex_owner_id_is_live(owner) {
|
||||
let new_value = (thread & WAITING_BIT) | FUTEX_OWNER_DIED | this_thread;
|
||||
match self.inner.compare_exchange(
|
||||
thread,
|
||||
new_value,
|
||||
Ordering::Acquire,
|
||||
Ordering::Relaxed,
|
||||
) {
|
||||
Ok(_) => return self.finish_lock_acquire(true),
|
||||
Err(_) => continue,
|
||||
}
|
||||
}
|
||||
|
||||
if spins_left > 0 {
|
||||
spins_left -= 1;
|
||||
core::hint::spin_loop();
|
||||
continue;
|
||||
}
|
||||
if crate::sync::futex_wait(&self.inner, thread, deadline)
|
||||
== FutexWaitResult::TimedOut
|
||||
{
|
||||
return Err(Errno(ETIMEDOUT));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn lock(&self) -> Result<(), Errno> {
|
||||
self.lock_inner(None)
|
||||
}
|
||||
pub fn lock_with_timeout(&self, deadline: ×pec) -> Result<(), Errno> {
|
||||
self.lock_inner(Some(deadline))
|
||||
}
|
||||
fn finish_lock_acquire(&self, owner_dead: bool) -> Result<(), Errno> {
|
||||
if self.ty == Ty::Recursive {
|
||||
self.increment_recursive_count()?;
|
||||
}
|
||||
if self.robust {
|
||||
add_to_robust_list(self);
|
||||
}
|
||||
|
||||
if owner_dead {
|
||||
Err(Errno(EOWNERDEAD))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
fn increment_recursive_count(&self) -> Result<(), Errno> {
|
||||
// We don't have to worry about asynchronous signals here, since pthread_mutex_trylock
|
||||
// is not async-signal-safe.
|
||||
//
|
||||
// TODO: Maybe just use Cell? Send/Sync doesn't matter much anyway, and will be
|
||||
// protected by the lock itself anyway.
|
||||
|
||||
let prev_recursive_count = self.recursive_count.load(Ordering::Relaxed);
|
||||
|
||||
if prev_recursive_count == RECURSIVE_COUNT_MAX_INCLUSIVE {
|
||||
return Err(Errno(EAGAIN));
|
||||
}
|
||||
|
||||
self.recursive_count
|
||||
.store(prev_recursive_count + 1, Ordering::Relaxed);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub fn try_lock(&self) -> Result<(), Errno> {
|
||||
let this_thread = os_tid_invalid_after_fork();
|
||||
|
||||
loop {
|
||||
let current = self.inner.load(Ordering::Relaxed);
|
||||
|
||||
if current == STATE_UNLOCKED {
|
||||
match self.inner.compare_exchange(
|
||||
STATE_UNLOCKED,
|
||||
this_thread,
|
||||
Ordering::Acquire,
|
||||
Ordering::Relaxed,
|
||||
) {
|
||||
Ok(_) => return self.finish_lock_acquire(false),
|
||||
Err(_) => continue,
|
||||
}
|
||||
}
|
||||
|
||||
let owner = current & INDEX_MASK;
|
||||
|
||||
if owner == this_thread && self.ty == Ty::Recursive {
|
||||
self.increment_recursive_count()?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if owner == this_thread && self.ty == Ty::Errck {
|
||||
return Err(Errno(EDEADLK));
|
||||
}
|
||||
|
||||
if self.robust && (current & FUTEX_OWNER_DIED != 0 || (owner != 0 && !crate::pthread::mutex_owner_id_is_live(owner))) {
|
||||
let new_value = (current & WAITING_BIT) | FUTEX_OWNER_DIED | this_thread;
|
||||
match self.inner.compare_exchange(
|
||||
current,
|
||||
new_value,
|
||||
Ordering::Acquire,
|
||||
Ordering::Relaxed,
|
||||
) {
|
||||
Ok(_) => return self.finish_lock_acquire(true),
|
||||
Err(_) => continue,
|
||||
}
|
||||
}
|
||||
|
||||
return Err(Errno(EBUSY));
|
||||
}
|
||||
}
|
||||
// Safe because we are not protecting any data.
|
||||
pub fn unlock(&self) -> Result<(), Errno> {
|
||||
let current = self.inner.load(Ordering::Relaxed);
|
||||
|
||||
if self.robust || matches!(self.ty, Ty::Recursive | Ty::Errck) {
|
||||
if current & INDEX_MASK != os_tid_invalid_after_fork() {
|
||||
return Err(Errno(EPERM));
|
||||
}
|
||||
|
||||
core::sync::atomic::fence(Ordering::Acquire);
|
||||
}
|
||||
|
||||
if self.ty == Ty::Recursive {
|
||||
let next = self.recursive_count.load(Ordering::Relaxed) - 1;
|
||||
self.recursive_count.store(next, Ordering::Relaxed);
|
||||
|
||||
if next > 0 {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
if self.robust {
|
||||
remove_from_robust_list(self);
|
||||
}
|
||||
|
||||
let new_state = if self.robust && current & FUTEX_OWNER_DIED != 0 {
|
||||
FUTEX_OWNER_DIED
|
||||
} else {
|
||||
STATE_UNLOCKED
|
||||
};
|
||||
|
||||
self.inner.store(new_state, Ordering::Release);
|
||||
crate::sync::futex_wake(&self.inner, i32::MAX);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn mark_robust_mutexes_dead(thread: &crate::pthread::Pthread) {
|
||||
let head = thread.robust_list_head.get();
|
||||
// Null guard: if the thread's robust_list_head is null
|
||||
// (e.g. the thread was created but never locked a robust
|
||||
// mutex, or the kernel's robust list walk is invoked for a
|
||||
// thread that doesn't support it), the list is empty and
|
||||
// there's nothing to do. Without this check, dereferencing
|
||||
// *head on a null pointer would be UB.
|
||||
if unsafe { (*head).is_null() } {
|
||||
return;
|
||||
}
|
||||
let this_thread = os_tid_invalid_after_fork();
|
||||
let mut node = unsafe { *head };
|
||||
|
||||
unsafe { *head = core::ptr::null_mut() };
|
||||
|
||||
while !node.is_null() {
|
||||
let next = unsafe { (*node).next };
|
||||
let mutex = unsafe { &*(*node).mutex };
|
||||
let current = mutex.inner.load(Ordering::Relaxed);
|
||||
|
||||
if current & INDEX_MASK == this_thread {
|
||||
mutex
|
||||
.inner
|
||||
.store((current & WAITING_BIT) | FUTEX_OWNER_DIED | this_thread, Ordering::Release);
|
||||
crate::sync::futex_wake(&mutex.inner, i32::MAX);
|
||||
}
|
||||
|
||||
unsafe { drop(Box::from_raw(node)) };
|
||||
node = next;
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(PartialEq)]
|
||||
enum Ty {
|
||||
// The only difference between PTHREAD_MUTEX_NORMAL and PTHREAD_MUTEX_DEFAULT appears to be
|
||||
// that "normal" mutexes deadlock if locked multiple times on the same thread, whereas
|
||||
// "default" mutexes are UB in that case. So we can treat them as being the same type.
|
||||
Normal,
|
||||
Def,
|
||||
|
||||
Errck,
|
||||
Recursive,
|
||||
}
|
||||
|
||||
// Children after fork can only call async-signal-safe functions until they exec.
|
||||
#[thread_local]
|
||||
static CACHED_OS_TID_INVALID_AFTER_FORK: Cell<u32> = Cell::new(0);
|
||||
|
||||
fn add_to_robust_list(mutex: &RlctMutex) {
|
||||
let thread = crate::pthread::current_thread().expect("current thread not present");
|
||||
let node_ptr = Box::into_raw(Box::new(RobustMutexNode {
|
||||
next: core::ptr::null_mut(),
|
||||
prev: core::ptr::null_mut(),
|
||||
mutex: core::ptr::from_ref(mutex),
|
||||
}));
|
||||
|
||||
unsafe {
|
||||
let head = thread.robust_list_head.get();
|
||||
if !(*head).is_null() {
|
||||
(**head).prev = node_ptr;
|
||||
}
|
||||
(*node_ptr).next = *head;
|
||||
*head = node_ptr;
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_from_robust_list(mutex: &RlctMutex) {
|
||||
let thread = match crate::pthread::current_thread() {
|
||||
Some(thread) => thread,
|
||||
None => return,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let mut node = *thread.robust_list_head.get();
|
||||
|
||||
while !node.is_null() {
|
||||
if core::ptr::eq((*node).mutex, core::ptr::from_ref(mutex)) {
|
||||
if !(*node).prev.is_null() {
|
||||
(*(*node).prev).next = (*node).next;
|
||||
} else {
|
||||
*thread.robust_list_head.get() = (*node).next;
|
||||
}
|
||||
|
||||
if !(*node).next.is_null() {
|
||||
(*(*node).next).prev = (*node).prev;
|
||||
}
|
||||
|
||||
drop(Box::from_raw(node));
|
||||
return;
|
||||
}
|
||||
|
||||
node = (*node).next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assumes TIDs are unique between processes, which I only know is true for Redox.
|
||||
fn os_tid_invalid_after_fork() -> u32 {
|
||||
// TODO: Coordinate better if using shared == PTHREAD_PROCESS_SHARED, with up to 2^32 separate
|
||||
// threads within possibly distinct processes, using the mutex. OS thread IDs on Redox are
|
||||
// pointer-sized, but relibc and POSIX uses int everywhere.
|
||||
|
||||
let value = CACHED_OS_TID_INVALID_AFTER_FORK.get();
|
||||
|
||||
if value == 0 {
|
||||
let tid = Sys::gettid();
|
||||
|
||||
assert_ne!(tid, -1, "failed to obtain current thread ID");
|
||||
|
||||
CACHED_OS_TID_INVALID_AFTER_FORK.set(tid as u32);
|
||||
|
||||
tid as u32
|
||||
} else {
|
||||
value
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use super::{AtomicLock, AttemptStatus};
|
||||
|
||||
const WAITING_BIT: u32 = 1 << 31;
|
||||
const UNLOCKED: u32 = 0;
|
||||
// We now have 2^32 - 1 possible thread ID values
|
||||
|
||||
pub struct ReentrantMutex<T> {
|
||||
lock: AtomicLock,
|
||||
content: UnsafeCell<T>,
|
||||
}
|
||||
unsafe impl<T: Send> Send for ReentrantMutex {}
|
||||
unsafe impl<T: Send> Sync for ReentrantMutex {}
|
||||
|
||||
impl<T> ReentrantMutex<T> {
|
||||
pub const fn new(context: T) -> Self {
|
||||
Self {
|
||||
lock: AtomicLock::new(UNLOCKED),
|
||||
content: UnsafeCell::new(content),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct ReentrantMutexGuard<'a, T: 'a> {
|
||||
mutex: &'a ReentrantMutex<T>,
|
||||
content: &'a T,
|
||||
}
|
||||
impl<'a, T> Deref for MutexGuard {
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
use core::{
|
||||
cell::UnsafeCell,
|
||||
fmt, ops,
|
||||
sync::atomic::{AtomicU32, Ordering},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::{Errno, Result},
|
||||
header::{
|
||||
bits_timespec::timespec,
|
||||
errno::{EINVAL, ETIMEDOUT},
|
||||
time::{CLOCK_MONOTONIC, CLOCK_REALTIME, timespec_realtime_to_monotonic},
|
||||
},
|
||||
platform::types::clockid_t,
|
||||
pthread::Pshared,
|
||||
};
|
||||
|
||||
pub struct InnerRwLock {
|
||||
state: AtomicU32,
|
||||
}
|
||||
// PTHREAD_RWLOCK_INITIALIZER is defined as "all zeroes".
|
||||
|
||||
const WAITING_WR: u32 = 1 << (u32::BITS - 1);
|
||||
const COUNT_MASK: u32 = WAITING_WR - 1;
|
||||
const EXCLUSIVE: u32 = COUNT_MASK;
|
||||
|
||||
// TODO: Optimize for short waits and long waits, using AtomicLock::wait_until, but still
|
||||
// supporting timeouts.
|
||||
// TODO: Add futex ops that use bitmasks.
|
||||
|
||||
impl InnerRwLock {
|
||||
pub const fn new(_pshared: Pshared) -> Self {
|
||||
Self {
|
||||
state: AtomicU32::new(0),
|
||||
}
|
||||
}
|
||||
fn translate_timeout(deadline: Option<(×pec, i32)>) -> Result<Option<timespec>, Errno> {
|
||||
let relative = match deadline {
|
||||
// FUTEX expect monotonic clock
|
||||
Some((abstime, CLOCK_MONOTONIC)) => Some(abstime.clone()),
|
||||
Some((abstime, CLOCK_REALTIME)) => {
|
||||
Some(timespec_realtime_to_monotonic(abstime.clone())?)
|
||||
}
|
||||
None => None,
|
||||
_ => {
|
||||
return Err(Errno(EINVAL));
|
||||
}
|
||||
};
|
||||
Ok(relative)
|
||||
}
|
||||
pub fn acquire_write_lock(
|
||||
&self,
|
||||
deadline: Option<(×pec, clockid_t)>,
|
||||
) -> Result<(), Errno> {
|
||||
let relative = Self::translate_timeout(deadline)?;
|
||||
let mut waiting_wr = self.state.load(Ordering::Relaxed) & WAITING_WR;
|
||||
|
||||
loop {
|
||||
match self.state.compare_exchange_weak(
|
||||
waiting_wr,
|
||||
EXCLUSIVE,
|
||||
Ordering::Acquire,
|
||||
Ordering::Relaxed,
|
||||
) {
|
||||
Ok(_) => break,
|
||||
Err(actual) => {
|
||||
let expected = actual;
|
||||
let expected = if actual & COUNT_MASK != EXCLUSIVE {
|
||||
// Set the exclusive bit, but only if we're waiting for readers, to avoid
|
||||
// reader starvation by overprioritizing write locks.
|
||||
self.state.fetch_or(WAITING_WR, Ordering::Relaxed);
|
||||
|
||||
actual | WAITING_WR
|
||||
} else {
|
||||
actual
|
||||
};
|
||||
waiting_wr = expected & WAITING_WR;
|
||||
|
||||
if actual & COUNT_MASK > 0 {
|
||||
match crate::sync::futex_wait(&self.state, expected, relative.as_ref()) {
|
||||
super::FutexWaitResult::TimedOut => return Err(Errno(ETIMEDOUT)),
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
// We must avoid blocking indefinitely in our `futex_wait()`, in this case
|
||||
// where it's possible that `self.state == expected` but our futex might
|
||||
// never be woken again, because it's possible that all other threads
|
||||
// already did their `futex_wake()` before we would've done our
|
||||
// `futex_wait()`.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub fn acquire_read_lock(&self, deadline: Option<(×pec, clockid_t)>) -> Result<(), Errno> {
|
||||
let relative = Self::translate_timeout(deadline)?;
|
||||
while let Err(old) = self.try_acquire_read_lock() {
|
||||
match crate::sync::futex_wait(&self.state, old, relative.as_ref()) {
|
||||
super::FutexWaitResult::TimedOut => return Err(Errno(ETIMEDOUT)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub fn try_acquire_read_lock(&self) -> Result<(), u32> {
|
||||
let mut cached = self.state.load(Ordering::Acquire);
|
||||
|
||||
loop {
|
||||
let waiting_wr = cached & WAITING_WR;
|
||||
let old = if cached & COUNT_MASK == EXCLUSIVE {
|
||||
0
|
||||
} else {
|
||||
cached & COUNT_MASK
|
||||
};
|
||||
let new = old + 1;
|
||||
|
||||
// TODO: Return with error code instead?
|
||||
assert_ne!(
|
||||
new & COUNT_MASK,
|
||||
EXCLUSIVE,
|
||||
"maximum number of rwlock readers reached"
|
||||
);
|
||||
|
||||
match self.state.compare_exchange_weak(
|
||||
(old & COUNT_MASK) | waiting_wr,
|
||||
new | waiting_wr,
|
||||
Ordering::Acquire,
|
||||
Ordering::Relaxed,
|
||||
) {
|
||||
Ok(_) => return Ok(()),
|
||||
|
||||
Err(value) if value & COUNT_MASK == EXCLUSIVE => return Err(value),
|
||||
Err(value) => {
|
||||
cached = value;
|
||||
// TODO: SCHED_YIELD?
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn try_acquire_write_lock(&self) -> Result<(), u32> {
|
||||
let mut waiting_wr = self.state.load(Ordering::Relaxed) & WAITING_WR;
|
||||
|
||||
loop {
|
||||
match self.state.compare_exchange_weak(
|
||||
waiting_wr,
|
||||
EXCLUSIVE,
|
||||
Ordering::Acquire,
|
||||
Ordering::Relaxed,
|
||||
) {
|
||||
Ok(_) => return Ok(()),
|
||||
Err(actual) if actual & COUNT_MASK > 0 => return Err(actual),
|
||||
Err(can_retry) => {
|
||||
waiting_wr = can_retry & WAITING_WR;
|
||||
|
||||
core::hint::spin_loop();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unlock(&self) {
|
||||
let state = self.state.load(Ordering::Relaxed);
|
||||
|
||||
if state & COUNT_MASK == EXCLUSIVE {
|
||||
// Unlocking a write lock.
|
||||
|
||||
// This discards the writer-waiting bit, in order to ensure some level of fairness
|
||||
// between read and write locks.
|
||||
self.state.store(0, Ordering::Release);
|
||||
|
||||
let _ = crate::sync::futex_wake(&self.state, i32::MAX);
|
||||
} else {
|
||||
// Unlocking a read lock. Subtract one from the reader count, but preserve the
|
||||
// WAITING_WR bit.
|
||||
|
||||
if self.state.fetch_sub(1, Ordering::Release) & COUNT_MASK == 1 {
|
||||
let _ = crate::sync::futex_wake(&self.state, i32::MAX);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RwLock<T: ?Sized> {
|
||||
inner: InnerRwLock,
|
||||
data: UnsafeCell<T>,
|
||||
}
|
||||
|
||||
unsafe impl<T: ?Sized + Send> Send for RwLock<T> {}
|
||||
unsafe impl<T: ?Sized + Send + Sync> Sync for RwLock<T> {}
|
||||
|
||||
impl<T> RwLock<T> {
|
||||
pub const fn new(val: T) -> Self {
|
||||
Self {
|
||||
inner: InnerRwLock::new(Pshared::Private),
|
||||
data: UnsafeCell::new(val),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> RwLock<T> {
|
||||
pub fn read(&self) -> ReadGuard<'_, T> {
|
||||
let _ = self.inner.acquire_read_lock(None);
|
||||
unsafe { ReadGuard::new(self) }
|
||||
}
|
||||
|
||||
pub fn write(&self) -> WriteGuard<'_, T> {
|
||||
let _ = self.inner.acquire_write_lock(None);
|
||||
unsafe { WriteGuard::new(self) }
|
||||
}
|
||||
|
||||
pub fn try_read(&self) -> Option<ReadGuard<'_, T>> {
|
||||
if self.inner.try_acquire_read_lock().is_ok() {
|
||||
Some(unsafe { ReadGuard::new(self) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_write(&self) -> Option<WriteGuard<'_, T>> {
|
||||
if self.inner.try_acquire_write_lock().is_ok() {
|
||||
Some(unsafe { WriteGuard::new(self) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ReadGuard<'a, T: ?Sized + 'a> {
|
||||
lock: &'a RwLock<T>,
|
||||
}
|
||||
|
||||
impl<T: ?Sized> !Send for ReadGuard<'_, T> {}
|
||||
unsafe impl<T: ?Sized + Sync> Sync for ReadGuard<'_, T> {}
|
||||
|
||||
impl<'a, T: ?Sized> ReadGuard<'a, T> {
|
||||
unsafe fn new(lock: &'a RwLock<T>) -> Self {
|
||||
Self { lock }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> ops::Deref for ReadGuard<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
// SAFETY: We have shared reference to the data.
|
||||
unsafe { &*self.lock.data.get() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> Drop for ReadGuard<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
self.lock.inner.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized + fmt::Debug> fmt::Debug for ReadGuard<'a, T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(&**self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized + fmt::Display> fmt::Display for ReadGuard<'a, T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&**self, f)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WriteGuard<'a, T: ?Sized + 'a> {
|
||||
lock: &'a RwLock<T>,
|
||||
}
|
||||
|
||||
impl<T: ?Sized> !Send for WriteGuard<'_, T> {}
|
||||
unsafe impl<T: ?Sized + Sync> Sync for WriteGuard<'_, T> {}
|
||||
|
||||
impl<'a, T: ?Sized> WriteGuard<'a, T> {
|
||||
unsafe fn new(lock: &'a RwLock<T>) -> Self {
|
||||
Self { lock }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> ops::Deref for WriteGuard<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
// SAFETY: We have exclusive reference to the data.
|
||||
unsafe { &*self.lock.data.get() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> ops::DerefMut for WriteGuard<'a, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
// SAFETY: We have exclusive reference to the data.
|
||||
unsafe { &mut *self.lock.data.get() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> Drop for WriteGuard<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
self.lock.inner.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized + fmt::Debug> fmt::Debug for WriteGuard<'a, T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(&**self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized + fmt::Display> fmt::Display for WriteGuard<'a, T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&**self, f)
|
||||
}
|
||||
}
|
||||
+69
-25
@@ -1,46 +1,90 @@
|
||||
// From https://www.remlab.net/op/futex-misc.shtml
|
||||
//TODO: improve implementation
|
||||
|
||||
use crate::platform::{types::*, Pal, Sys};
|
||||
use super::AtomicLock;
|
||||
use core::sync::atomic::Ordering;
|
||||
use crate::{
|
||||
header::{
|
||||
bits_timespec::timespec,
|
||||
errno::{EINTR, ETIMEDOUT},
|
||||
time::{CLOCK_MONOTONIC, CLOCK_REALTIME, timespec_realtime_to_monotonic},
|
||||
},
|
||||
platform::{
|
||||
ERRNO,
|
||||
types::{c_int, c_uint, clockid_t},
|
||||
},
|
||||
};
|
||||
|
||||
use core::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
pub struct Semaphore {
|
||||
lock: AtomicLock,
|
||||
count: AtomicU32,
|
||||
}
|
||||
|
||||
impl Semaphore {
|
||||
pub const fn new(value: c_int) -> Self {
|
||||
pub const fn new(value: c_uint) -> Self {
|
||||
Self {
|
||||
lock: AtomicLock::new(value),
|
||||
count: AtomicU32::new(value),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn post(&self) {
|
||||
self.lock.fetch_add(1, Ordering::Relaxed);
|
||||
self.lock.notify_one();
|
||||
// TODO: Acquire-Release ordering?
|
||||
|
||||
pub fn post(&self, count: c_uint) {
|
||||
self.count.fetch_add(count, Ordering::SeqCst);
|
||||
// TODO: notify one?
|
||||
crate::sync::futex_wake(&self.count, i32::MAX);
|
||||
}
|
||||
|
||||
pub fn wait(&self) {
|
||||
let mut value = 1;
|
||||
|
||||
pub fn try_wait(&self) -> u32 {
|
||||
loop {
|
||||
match self.lock.compare_exchange_weak(
|
||||
value,
|
||||
value - 1,
|
||||
Ordering::Acquire,
|
||||
Ordering::Relaxed
|
||||
) {
|
||||
Ok(ok) => return,
|
||||
Err(err) => {
|
||||
value = err;
|
||||
}
|
||||
}
|
||||
let value = self.count.load(Ordering::SeqCst);
|
||||
|
||||
if value == 0 {
|
||||
self.lock.wait_if(0);
|
||||
value = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
match self.count.compare_exchange_weak(
|
||||
value,
|
||||
value - 1,
|
||||
Ordering::SeqCst,
|
||||
Ordering::SeqCst,
|
||||
) {
|
||||
Ok(_) => {
|
||||
// Acquired
|
||||
return value;
|
||||
}
|
||||
Err(_) => (),
|
||||
}
|
||||
// Try again (as long as value > 0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wait(&self, timeout_opt: Option<×pec>, clock_id: clockid_t) -> Result<(), c_int> {
|
||||
loop {
|
||||
let value = self.try_wait();
|
||||
|
||||
if value == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(timeout) = timeout_opt {
|
||||
let relative = match clock_id {
|
||||
CLOCK_MONOTONIC => timeout.clone(),
|
||||
CLOCK_REALTIME => match timespec_realtime_to_monotonic(timeout.clone()) {
|
||||
Ok(relative) => relative,
|
||||
Err(_) => return Err(ETIMEDOUT),
|
||||
},
|
||||
_ => return Err(ETIMEDOUT),
|
||||
};
|
||||
crate::sync::futex_wait(&self.count, value, Some(&relative));
|
||||
} else {
|
||||
crate::sync::futex_wait(&self.count, value, None);
|
||||
}
|
||||
if ERRNO.get() == EINTR {
|
||||
return Err(EINTR);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn value(&self) -> c_uint {
|
||||
self.count.load(Ordering::SeqCst)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
use core::{
|
||||
cell::UnsafeCell,
|
||||
mem::MaybeUninit,
|
||||
sync::atomic::{AtomicU32 as AtomicUint, Ordering},
|
||||
};
|
||||
|
||||
/// An unsafe "one thread to one thread" synchronization primitive. Used for and modeled after
|
||||
/// pthread_join only, at the moment.
|
||||
#[derive(Debug)]
|
||||
pub struct Waitval<T> {
|
||||
state: AtomicUint,
|
||||
value: UnsafeCell<MaybeUninit<T>>,
|
||||
}
|
||||
|
||||
unsafe impl<T: Send + Sync> Send for Waitval<T> {}
|
||||
unsafe impl<T: Send + Sync> Sync for Waitval<T> {}
|
||||
|
||||
impl<T> Waitval<T> {
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
state: AtomicUint::new(0),
|
||||
value: UnsafeCell::new(MaybeUninit::uninit()),
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: Caller must ensure both (1) that the value has not yet been initialized, and (2)
|
||||
// that this is never run by more than one thread simultaneously.
|
||||
pub unsafe fn post(&self, value: T) {
|
||||
unsafe { self.value.get().write(MaybeUninit::new(value)) };
|
||||
self.state.store(1, Ordering::Release);
|
||||
crate::sync::futex_wake(&self.state, i32::MAX);
|
||||
}
|
||||
|
||||
pub fn wait(&self) -> &T {
|
||||
while self.state.load(Ordering::Acquire) == 0 {
|
||||
crate::sync::futex_wait(&self.state, 0, None);
|
||||
}
|
||||
|
||||
unsafe { (*self.value.get()).assume_init_ref() }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user