0.3.0: converge relibc to upstream 0.6.0 + Red Bear patches

This commit is contained in:
2026-07-06 19:13:08 +03:00
parent 1a0edd8eeb
commit 4ef7e57571
1466 changed files with 75236 additions and 13644 deletions
+76
View File
@@ -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
}
}
}
+91
View File
@@ -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;
}
}
}
+140
View File
@@ -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: &timespec,
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: &timespec) -> 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<&timespec>) -> 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<&timespec>,
) -> 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
View File
@@ -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<&timespec>,
) -> 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<&timespec>,
) -> 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<&timespec>) {
self.wait_if_raw(value, timeout_opt);
}
pub fn wait_if_raw(&self, value: c_int, timeout_opt: Option<&timespec>) -> 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
View File
@@ -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
View File
@@ -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();
}
}
}
}
*/
+404
View File
@@ -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<&timespec>) -> 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: &timespec) -> 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
}
}
+27
View File
@@ -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 {
}
+318
View File
@@ -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<(&timespec, 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<(&timespec, 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<(&timespec, 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
View File
@@ -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<&timespec>, 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)
}
}
+42
View File
@@ -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() }
}
}