ax-std 0.7.2

ArceOS user library with an interface similar to rust std
Documentation
use core::time::Duration;

const NANOS_PER_SECOND: libc::c_long = 1_000_000_000;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(super) enum TimeoutMode {
    Relative,
    AbsoluteMonotonic,
    AbsoluteRealtime,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(super) struct ClockSnapshot {
    monotonic: Duration,
    realtime: Duration,
}

impl ClockSnapshot {
    pub(super) const fn new(monotonic: Duration, realtime: Duration) -> Self {
        Self {
            monotonic,
            realtime,
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(super) struct InvalidTimespec;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(super) enum WaitError {
    ValueMismatch,
    InvalidTimeout,
}

pub(super) fn prepare_wait_timeout(
    actual: u32,
    expected: u32,
    parse_timeout: impl FnOnce() -> Result<Option<Duration>, InvalidTimespec>,
) -> Result<Option<Duration>, WaitError> {
    if actual != expected {
        return Err(WaitError::ValueMismatch);
    }
    parse_timeout().map_err(|_| WaitError::InvalidTimeout)
}

pub(super) fn timeout_from_timespec(
    ts: libc::timespec,
    mode: TimeoutMode,
    clocks: ClockSnapshot,
) -> Result<Duration, InvalidTimespec> {
    if ts.tv_sec < 0 || !(0..NANOS_PER_SECOND).contains(&ts.tv_nsec) {
        return Err(InvalidTimespec);
    }

    let timeout = Duration::new(ts.tv_sec as u64, ts.tv_nsec as u32);
    let duration = match mode {
        TimeoutMode::Relative => timeout,
        TimeoutMode::AbsoluteMonotonic => timeout.saturating_sub(clocks.monotonic),
        TimeoutMode::AbsoluteRealtime => timeout.saturating_sub(clocks.realtime),
    };
    Ok(duration)
}

#[cfg(all(test, feature = "host-test"))]
mod tests {
    use super::*;

    #[test]
    fn futex_wait_timeout_is_relative_to_the_call() {
        let clocks = clocks_at(10_000, 1_000_000);

        assert_eq!(
            timeout_from_timespec(timespec_at(100), TimeoutMode::Relative, clocks),
            Ok(Duration::from_millis(100))
        );
    }

    #[test]
    fn futex_wait_bitset_uses_absolute_monotonic_deadline() {
        let clocks = clocks_at(10_000, 1_000_000);

        assert_eq!(
            timeout_from_timespec(timespec_at(10_100), TimeoutMode::AbsoluteMonotonic, clocks,),
            Ok(Duration::from_millis(100))
        );
    }

    #[test]
    fn futex_wait_bitset_realtime_uses_absolute_realtime_deadline() {
        let clocks = clocks_at(10_000, 1_000_000);

        assert_eq!(
            timeout_from_timespec(
                timespec_at(1_000_100),
                TimeoutMode::AbsoluteRealtime,
                clocks,
            ),
            Ok(Duration::from_millis(100))
        );
    }

    #[test]
    fn expired_absolute_futex_timeout_is_immediate() {
        let clocks = clocks_at(10_000, 1_000_000);

        assert_eq!(
            timeout_from_timespec(timespec_at(9_000), TimeoutMode::AbsoluteMonotonic, clocks,),
            Ok(Duration::ZERO)
        );
        assert_eq!(
            timeout_from_timespec(timespec_at(999_000), TimeoutMode::AbsoluteRealtime, clocks,),
            Ok(Duration::ZERO)
        );
    }

    #[test]
    fn futex_timeout_rejects_denormal_timespec() {
        let clocks = clocks_at(10_000, 1_000_000);

        for timeout in [
            libc::timespec {
                tv_sec: -1,
                tv_nsec: 0,
            },
            libc::timespec {
                tv_sec: 0,
                tv_nsec: -1,
            },
            libc::timespec {
                tv_sec: 0,
                tv_nsec: NANOS_PER_SECOND,
            },
        ] {
            assert_eq!(
                timeout_from_timespec(timeout, TimeoutMode::Relative, clocks),
                Err(InvalidTimespec)
            );
        }
    }

    #[test]
    fn futex_value_mismatch_takes_precedence_over_invalid_timeout() {
        assert_eq!(
            prepare_wait_timeout(1, 0, || Err(InvalidTimespec)),
            Err(WaitError::ValueMismatch)
        );
        assert_eq!(
            prepare_wait_timeout(0, 0, || Err(InvalidTimespec)),
            Err(WaitError::InvalidTimeout)
        );
    }

    fn clocks_at(monotonic_ms: u64, realtime_ms: u64) -> ClockSnapshot {
        ClockSnapshot::new(
            Duration::from_millis(monotonic_ms),
            Duration::from_millis(realtime_ms),
        )
    }

    fn timespec_at(milliseconds: i64) -> libc::timespec {
        libc::timespec {
            tv_sec: milliseconds / 1_000,
            tv_nsec: (milliseconds % 1_000) * 1_000_000,
        }
    }
}