202e3eb500
Added clock: u8 field to Cond struct that stores the clock setting (CLOCK_REALTIME or CLOCK_MONOTONIC) per pthread_condattr. pthread_cond_init now properly sets the clock attribute. timedwait uses the stored clock instead of hardcoding CLOCK_REALTIME. Previously pthread_cond_init with CLOCK_MONOTONIC would log TODO and use the wrong clock, causing condition variable timeouts to behave incorrectly. pthread_cond_t size updated from 8 to 12 bytes to accommodate the new field. Cross-referenced with POSIX pthread_cond_init(3) and Linux glibc nptl pthread_cond_init.c.
148 lines
4.5 KiB
Rust
148 lines
4.5 KiB
Rust
// 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 {
|
|
pub cur: AtomicUint,
|
|
pub prev: AtomicUint,
|
|
/// Clock used for timedwait, per pthread_condattr_setclock.
|
|
/// CLOCK_REALTIME (0) or CLOCK_MONOTONIC (1). Set once at init.
|
|
pub clock: u8,
|
|
/// Padding to maintain 12-byte struct size for ABI compatibility.
|
|
pub _pad: [u8; 3],
|
|
}
|
|
|
|
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),
|
|
clock: CLOCK_REALTIME as u8,
|
|
_pad: [0; 3],
|
|
}
|
|
}
|
|
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> {
|
|
let clock_id = self.clock as clockid_t;
|
|
self.clockwait(mutex, timeout, clock_id)
|
|
}
|
|
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)
|
|
}
|
|
}
|