Use artificial time instead

This commit is contained in:
datdenkikniet 2023-04-10 13:05:36 +02:00
parent 25c2c59a42
commit 5bcbe92f55
2 changed files with 198 additions and 135 deletions

View file

@ -23,6 +23,4 @@ rtic-common = { version = "1.0.0-alpha.0", path = "../rtic-common" }
[dev-dependencies] [dev-dependencies]
parking_lot = "0.12" parking_lot = "0.12"
tokio = { version = "1.27", features = ["rt", "macros", "sync", "rt-multi-thread", "time"] } cassette = "0.2"
pretty_env_logger = "0.4"
log = "0.4"

View file

@ -2,29 +2,107 @@
//! //!
//! To run this test, you need to activate the `critical-section/std` feature. //! To run this test, you need to activate the `critical-section/std` feature.
use std::{fmt::Debug, time::Duration}; use std::{
fmt::Debug,
task::{Poll, Waker},
};
use cassette::{pin_mut, Cassette};
use parking_lot::Mutex; use parking_lot::Mutex;
use rtic_time::{Monotonic, TimerQueue}; use rtic_time::{Monotonic, TimerQueue};
use tokio::sync::watch;
static START: Mutex<Option<std::time::Instant>> = Mutex::new(None); static NOW: Mutex<Option<Instant>> = Mutex::new(None);
pub struct StdTokioMono;
// An instant that "starts" at Duration::ZERO, so we can
// have a zero value.
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug)] #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug)]
pub struct Instant(std::time::Duration); pub struct Duration(u64);
impl Duration {
pub fn from_ticks(millis: u64) -> Self {
Self(millis)
}
pub fn as_ticks(&self) -> u64 {
self.0
}
}
impl core::ops::Add<Duration> for Duration {
type Output = Duration;
fn add(self, rhs: Duration) -> Self::Output {
Self(self.0 + rhs.0)
}
}
impl From<Duration> for Instant {
fn from(value: Duration) -> Self {
Instant(value.0)
}
}
static WAKERS: Mutex<Vec<Waker>> = Mutex::new(Vec::new());
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug)]
pub struct Instant(u64);
impl Instant { impl Instant {
pub fn init() { const ZERO: Self = Self(0);
assert!(START.lock().is_none());
let _ = START.lock().insert(std::time::Instant::now()); pub fn tick() -> bool {
// If we've never ticked before, initialize the clock.
if NOW.lock().is_none() {
*NOW.lock() = Some(Instant::ZERO);
}
// We've ticked before, add one to the clock
else {
let now = Instant::now();
let new_time = now + Duration(1);
*NOW.lock() = Some(new_time);
}
let had_wakers = !WAKERS.lock().is_empty();
// Wake up all things waiting for a specific time to happen.
for waker in WAKERS.lock().drain(..) {
waker.wake_by_ref();
}
let had_interrupt = TestMono::tick(false);
had_interrupt || had_wakers
} }
pub fn now() -> Self { pub fn now() -> Self {
let start = channel_read("Instant start not initialized", &START); NOW.lock().clone().unwrap_or(Instant::ZERO)
Self(start.elapsed()) }
pub fn from_ticks(ticks: u64) -> Self {
Self(ticks)
}
pub fn as_ticks(&self) -> u64 {
self.0
}
pub fn elapsed(&self) -> Duration {
Duration(Self::now().0 - self.0)
}
pub async fn sleep_until(time: Instant) {
core::future::poll_fn(|ctx| {
if Instant::now() >= time {
Poll::Ready(())
} else {
WAKERS.lock().push(ctx.waker().clone());
Poll::Pending
}
})
.await;
}
}
impl From<u64> for Instant {
fn from(value: u64) -> Self {
Self::from_ticks(value)
} }
} }
@ -32,7 +110,7 @@ impl core::ops::Add<Duration> for Instant {
type Output = Instant; type Output = Instant;
fn add(self, rhs: Duration) -> Self::Output { fn add(self, rhs: Duration) -> Self::Output {
Self(self.0 + rhs) Self(self.0 + rhs.0)
} }
} }
@ -40,7 +118,7 @@ impl core::ops::Sub<Duration> for Instant {
type Output = Instant; type Output = Instant;
fn sub(self, rhs: Duration) -> Self::Output { fn sub(self, rhs: Duration) -> Self::Output {
Self(self.0 - rhs) Self(self.0 - rhs.0)
} }
} }
@ -48,104 +126,49 @@ impl core::ops::Sub<Instant> for Instant {
type Output = Duration; type Output = Duration;
fn sub(self, rhs: Instant) -> Self::Output { fn sub(self, rhs: Instant) -> Self::Output {
self.0 - rhs.0 Duration(self.0 - rhs.0)
} }
} }
fn channel_read<T: Clone>(msg: &str, channel: &Mutex<Option<T>>) -> T { static COMPARE: Mutex<Option<Instant>> = Mutex::new(None);
channel.lock().as_ref().expect(msg).clone() static TIMER_QUEUE: TimerQueue<TestMono> = TimerQueue::new();
}
fn event_write<T: Debug>(msg: &str, channel: &Mutex<Option<watch::Sender<T>>>, value: T) { pub struct TestMono;
channel.lock().as_ref().expect(msg).send(value).unwrap()
}
static COMPARE_RX: Mutex<Option<watch::Receiver<Instant>>> = Mutex::new(None); impl TestMono {
static COMPARE_TX: Mutex<Option<watch::Sender<Instant>>> = Mutex::new(None); pub fn tick(force_interrupt: bool) -> bool {
static INTERRUPT_RX: Mutex<Option<watch::Receiver<()>>> = Mutex::new(None); let now = Instant::now();
static INTERRUPT_TX: Mutex<Option<watch::Sender<()>>> = Mutex::new(None);
let compare_reached = Some(now) == Self::compare();
let interrupt = compare_reached || force_interrupt;
if interrupt {
unsafe {
TestMono::queue().on_monotonic_interrupt();
}
true
} else {
false
}
}
impl StdTokioMono {
/// Initialize the monotonic. /// Initialize the monotonic.
/// pub fn init() {
/// Returns a [`watch::Sender`] that will cause the interrupt
/// & compare-change tasks to exit if a value is sent to it or it
/// is dropped.
#[must_use = "Dropping the returned Sender stops interrupts & compare-change events"]
pub fn init() -> watch::Sender<()> {
Instant::init();
let (compare_tx, compare_rx) = watch::channel(Instant(Duration::ZERO));
let (irq_tx, irq_rx) = watch::channel(());
assert!(COMPARE_RX.lock().is_none());
assert!(COMPARE_TX.lock().is_none());
let _ = COMPARE_RX.lock().insert(compare_rx);
let _ = COMPARE_TX.lock().insert(compare_tx);
assert!(INTERRUPT_RX.lock().is_none());
assert!(INTERRUPT_TX.lock().is_none());
let _ = INTERRUPT_RX.lock().insert(irq_rx);
let _ = INTERRUPT_TX.lock().insert(irq_tx);
Self::queue().initialize(Self); Self::queue().initialize(Self);
let (killer_tx, mut killer_rx) = watch::channel(());
let mut killer_clone = killer_rx.clone();
// Set up a task that watches for changes to the COMPARE value,
// and re-starts a timeout based on that change
tokio::spawn(async move {
let mut compare_rx = channel_read("Compare RX not initialized", &COMPARE_RX);
loop {
let compare = compare_rx.borrow().clone();
let end = channel_read("Start not initialized", &START) + compare.0;
tokio::select! {
_ = killer_clone.changed() => break,
_ = compare_rx.changed() => {},
_ = tokio::time::sleep_until(end.into()) => {
event_write("Interrupt TX not initialized", &INTERRUPT_TX, ());
// Sleep for a bit to avoid re-firing the interrupt a bunch of
// times.
tokio::time::sleep(Duration::from_millis(1)).await;
},
}
}
});
// Set up a task that emulates an interrupt handler, calling `on_monotonic_interrupt`
// whenever an "interrupt" is generated.
tokio::spawn(async move {
let mut interrupt_rx = channel_read("Interrupt RX not initialized.", &INTERRUPT_RX);
loop {
tokio::select! {
_ = killer_rx.changed() => break,
_ = interrupt_rx.changed() => {
// TODO: verify that we get interrupts triggered by an
// explicit pend or due to COMPARE at the correct time.
}
}
unsafe {
StdTokioMono::queue().on_monotonic_interrupt();
}
}
});
killer_tx
} }
/// Used to access the underlying timer queue /// Used to access the underlying timer queue
pub fn queue() -> &'static TimerQueue<StdTokioMono> { pub fn queue() -> &'static TimerQueue<TestMono> {
&TIMER_QUEUE &TIMER_QUEUE
} }
pub fn compare() -> Option<Instant> {
COMPARE.lock().clone()
}
} }
impl Monotonic for StdTokioMono { impl Monotonic for TestMono {
const ZERO: Self::Instant = Instant(Duration::ZERO); const ZERO: Self::Instant = Instant::ZERO;
type Instant = Instant; type Instant = Instant;
@ -156,66 +179,108 @@ impl Monotonic for StdTokioMono {
} }
fn set_compare(instant: Self::Instant) { fn set_compare(instant: Self::Instant) {
// TODO: verify that we receive the correct amount & values let _ = COMPARE.lock().insert(instant);
// for `set_compare`.
log::info!("Setting compare to {} ms", instant.0.as_millis());
event_write("Compare TX not initialized", &COMPARE_TX, instant);
} }
fn clear_compare_flag() {} fn clear_compare_flag() {}
fn pend_interrupt() { fn pend_interrupt() {
event_write("Interrupt TX not initialized", &INTERRUPT_TX, ()); Self::tick(true);
} }
} }
static TIMER_QUEUE: TimerQueue<StdTokioMono> = TimerQueue::new(); #[test]
fn timer_queue() {
TestMono::init();
let start = Instant::ZERO;
#[tokio::test] let build_delay_test = |pre_delay: Option<u64>, delay: u64| {
async fn main() { let delay = Duration::from_ticks(delay);
pretty_env_logger::init(); let pre_delay = pre_delay.map(Duration::from_ticks);
let _interrupt_killer = StdTokioMono::init();
let start = std::time::Instant::now();
let build_delay_test = |threshold: u128, pre_delay: Option<u64>, delay: u64| {
let delay = Duration::from_millis(delay);
let pre_delay = pre_delay.map(Duration::from_millis);
let total = if let Some(pre_delay) = pre_delay { let total = if let Some(pre_delay) = pre_delay {
pre_delay + delay pre_delay + delay
} else { } else {
delay delay
}; };
let total_millis = total.as_millis(); let total_millis = total.as_ticks();
async move { async move {
if let Some(pre_delay) = pre_delay { if let Some(pre_delay) = pre_delay {
tokio::time::sleep_until((start + pre_delay).into()).await; Instant::sleep_until(start + pre_delay).await;
} }
StdTokioMono::queue().delay(delay).await; TestMono::queue().delay(delay).await;
let elapsed = start.elapsed().as_millis(); let elapsed = start.elapsed().as_ticks();
log::info!("{total_millis} ms delay reached (after {elapsed} ms)"); println!("{total_millis} ticks delay reached after {elapsed} ticks");
if elapsed > total_millis.saturating_add(threshold) if elapsed != total_millis {
|| elapsed < total_millis.saturating_sub(threshold) panic!(
{ "{total_millis} ticks delay was not on time ({elapsed} ticks passed instead)"
panic!("{total_millis} ms delay was not on time ({elapsed} ms passed instead)"); );
} }
} }
}; };
// TODO: depending on the precision of the delays that can be used, this threshold let d1 = build_delay_test(Some(100), 100);
// may have to be altered a bit. pin_mut!(d1);
const TIME_THRESHOLD_MS: u128 = 5; let mut d1 = Cassette::new(d1);
let sec1 = build_delay_test(TIME_THRESHOLD_MS, Some(100), 100); let d2 = build_delay_test(None, 300);
let sec2 = build_delay_test(TIME_THRESHOLD_MS, None, 300); pin_mut!(d2);
let sec3 = build_delay_test(TIME_THRESHOLD_MS, None, 400); let mut d2 = Cassette::new(d2);
tokio::join!(sec2, sec1, sec3); let d3 = build_delay_test(None, 400);
pin_mut!(d3);
let mut d3 = Cassette::new(d3);
macro_rules! try_poll {
($fut:ident) => {
if !$fut.is_done() {
$fut.poll_on();
}
};
}
// Do an initial poll to set up all of the waiting futures
try_poll!(d1);
try_poll!(d2);
try_poll!(d3);
for _ in 0..500 {
// We only poll the waiting futures if an
// interrupt occured or if an artificial delay
// has passed.
if Instant::tick() {
try_poll!(d1);
try_poll!(d2);
try_poll!(d3);
}
if Instant::now() == 0.into() {
// First, we want to be waiting for our 300 tick delay
assert_eq!(TestMono::compare(), Some(300.into()));
}
if Instant::now() == 100.into() {
// After 100 ticks, we enqueue a new delay that is supposed to last
// until the 200-tick-mark
assert_eq!(TestMono::compare(), Some(200.into()));
}
if Instant::now() == 200.into() {
// After 200 ticks, we dequeue the 200-tick-mark delay and
// requeue the 300 tick delay
assert_eq!(TestMono::compare(), Some(300.into()));
}
if Instant::now() == 300.into() {
// After 300 ticks, we dequeue the 300-tick-mark delay and
// go to the 400 tick delay that is already enqueued
assert_eq!(TestMono::compare(), Some(400.into()));
}
}
assert!(d1.is_done() && d2.is_done() && d3.is_done());
} }