From d445b20b1853e2e9de60b24e0ed04a463c8cd05f Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Mon, 10 Apr 2023 10:34:00 +0200 Subject: [PATCH 1/6] Add test for timer queue & monotonic --- rtic-time/Cargo.toml | 7 ++ rtic-time/tests/timer_queue.rs | 217 +++++++++++++++++++++++++++++++++ 2 files changed, 224 insertions(+) create mode 100644 rtic-time/tests/timer_queue.rs diff --git a/rtic-time/Cargo.toml b/rtic-time/Cargo.toml index 462ad5d2b5..dda8e600cc 100644 --- a/rtic-time/Cargo.toml +++ b/rtic-time/Cargo.toml @@ -20,3 +20,10 @@ license = "MIT OR Apache-2.0" critical-section = "1" futures-util = { version = "0.3.25", default-features = false } rtic-common = { version = "1.0.0-alpha.0", path = "../rtic-common" } + +[dev-dependencies] +parking_lot = "0.12" +tokio = { version = "1.27", features = ["rt", "macros", "sync", "rt-multi-thread", "time"] } +critical-section = { version = "1", features = ["std"] } +pretty_env_logger = "0.4" +log = "0.4" diff --git a/rtic-time/tests/timer_queue.rs b/rtic-time/tests/timer_queue.rs new file mode 100644 index 0000000000..1ac96b80fd --- /dev/null +++ b/rtic-time/tests/timer_queue.rs @@ -0,0 +1,217 @@ +use std::{fmt::Debug, time::Duration}; + +use parking_lot::Mutex; +use rtic_time::{Monotonic, TimerQueue}; +use tokio::sync::watch; + +static START: Mutex> = 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)] +pub struct Instant(std::time::Duration); + +impl Instant { + pub fn init() { + assert!(START.lock().is_none()); + let _ = START.lock().insert(std::time::Instant::now()); + } + + pub fn now() -> Self { + let start = channel_read("Instant start not initialized", &START); + Self(start.elapsed()) + } +} + +impl core::ops::Add for Instant { + type Output = Instant; + + fn add(self, rhs: Duration) -> Self::Output { + Self(self.0 + rhs) + } +} + +impl core::ops::Sub for Instant { + type Output = Instant; + + fn sub(self, rhs: Duration) -> Self::Output { + Self(self.0 - rhs) + } +} + +impl core::ops::Sub for Instant { + type Output = Duration; + + fn sub(self, rhs: Instant) -> Self::Output { + self.0 - rhs.0 + } +} + +fn channel_read(msg: &str, channel: &Mutex>) -> T { + channel.lock().as_ref().expect(msg).clone() +} + +fn event_write(msg: &str, channel: &Mutex>>, value: T) { + channel.lock().as_ref().expect(msg).send(value).unwrap() +} + +static COMPARE_RX: Mutex>> = Mutex::new(None); +static COMPARE_TX: Mutex>> = Mutex::new(None); +static INTERRUPT_RX: Mutex>> = Mutex::new(None); +static INTERRUPT_TX: Mutex>> = Mutex::new(None); + +impl StdTokioMono { + /// Initialize the monotonic. + /// + /// 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); + + 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 + pub fn queue() -> &'static TimerQueue { + &TIMER_QUEUE + } +} + +impl Monotonic for StdTokioMono { + const ZERO: Self::Instant = Instant(Duration::ZERO); + + type Instant = Instant; + + type Duration = Duration; + + fn now() -> Self::Instant { + Instant::now() + } + + fn set_compare(instant: Self::Instant) { + // TODO: verify that we receive the correct amount & values + // 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 pend_interrupt() { + event_write("Interrupt TX not initialized", &INTERRUPT_TX, ()); + } +} + +static TIMER_QUEUE: TimerQueue = TimerQueue::new(); + +#[tokio::test] +async fn main() { + pretty_env_logger::init(); + + let _interrupt_killer = StdTokioMono::init(); + + let start = std::time::Instant::now(); + + let build_delay_test = |threshold: u128, pre_delay: Option, 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 { + pre_delay + delay + } else { + delay + }; + let total_millis = total.as_millis(); + async move { + if let Some(pre_delay) = pre_delay { + tokio::time::sleep_until((start + pre_delay).into()).await; + } + + StdTokioMono::queue().delay(delay).await; + + let elapsed = start.elapsed().as_millis(); + log::info!("{total_millis} ms delay reached (after {elapsed} ms)"); + + if elapsed > total_millis.saturating_add(threshold) + || elapsed < total_millis.saturating_sub(threshold) + { + 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 + // may have to be altered a bit. + const TIME_THRESHOLD_MS: u128 = 5; + + let sec1 = build_delay_test(TIME_THRESHOLD_MS, Some(100), 100); + let sec2 = build_delay_test(TIME_THRESHOLD_MS, None, 300); + let sec3 = build_delay_test(TIME_THRESHOLD_MS, None, 400); + + tokio::join!(sec2, sec1, sec3); +} From 452bad69b4b6d08b9e13e8ddf881385c6806c233 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Mon, 10 Apr 2023 10:52:21 +0200 Subject: [PATCH 2/6] Do not activate critical-section/std by default --- rtic-time/Cargo.toml | 1 - rtic-time/tests/timer_queue.rs | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/rtic-time/Cargo.toml b/rtic-time/Cargo.toml index dda8e600cc..69205fd01e 100644 --- a/rtic-time/Cargo.toml +++ b/rtic-time/Cargo.toml @@ -24,6 +24,5 @@ rtic-common = { version = "1.0.0-alpha.0", path = "../rtic-common" } [dev-dependencies] parking_lot = "0.12" tokio = { version = "1.27", features = ["rt", "macros", "sync", "rt-multi-thread", "time"] } -critical-section = { version = "1", features = ["std"] } pretty_env_logger = "0.4" log = "0.4" diff --git a/rtic-time/tests/timer_queue.rs b/rtic-time/tests/timer_queue.rs index 1ac96b80fd..cccf04dd92 100644 --- a/rtic-time/tests/timer_queue.rs +++ b/rtic-time/tests/timer_queue.rs @@ -1,3 +1,7 @@ +//! A test that verifies the correctness of the [`TimerQueue`]. +//! +//! To run this test, you need to activate the `critical-section/std` feature. + use std::{fmt::Debug, time::Duration}; use parking_lot::Mutex; From 25c2c59a42e97d3beab7d2958fd82f8506333752 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Mon, 10 Apr 2023 10:57:22 +0200 Subject: [PATCH 3/6] Add feature to xtask test for rtic-time --- xtask/src/argument_parsing.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xtask/src/argument_parsing.rs b/xtask/src/argument_parsing.rs index 8795213215..c0538e204e 100644 --- a/xtask/src/argument_parsing.rs +++ b/xtask/src/argument_parsing.rs @@ -71,7 +71,7 @@ impl TestMetadata { }, Package::RticTime => CargoCommand::Test { package: Some(package), - features: None, + features: Some("critical-section/std".into()), test: None, }, } From 5bcbe92f55e2376be0717636f6d4e30227040496 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Mon, 10 Apr 2023 13:05:36 +0200 Subject: [PATCH 4/6] Use artificial time instead --- rtic-time/Cargo.toml | 4 +- rtic-time/tests/timer_queue.rs | 329 ++++++++++++++++++++------------- 2 files changed, 198 insertions(+), 135 deletions(-) diff --git a/rtic-time/Cargo.toml b/rtic-time/Cargo.toml index 69205fd01e..b0746c17cb 100644 --- a/rtic-time/Cargo.toml +++ b/rtic-time/Cargo.toml @@ -23,6 +23,4 @@ rtic-common = { version = "1.0.0-alpha.0", path = "../rtic-common" } [dev-dependencies] parking_lot = "0.12" -tokio = { version = "1.27", features = ["rt", "macros", "sync", "rt-multi-thread", "time"] } -pretty_env_logger = "0.4" -log = "0.4" +cassette = "0.2" diff --git a/rtic-time/tests/timer_queue.rs b/rtic-time/tests/timer_queue.rs index cccf04dd92..7b233740b0 100644 --- a/rtic-time/tests/timer_queue.rs +++ b/rtic-time/tests/timer_queue.rs @@ -2,29 +2,107 @@ //! //! 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 rtic_time::{Monotonic, TimerQueue}; -use tokio::sync::watch; -static START: Mutex> = Mutex::new(None); -pub struct StdTokioMono; +static NOW: Mutex> = Mutex::new(None); -// An instant that "starts" at Duration::ZERO, so we can -// have a zero value. #[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 for Duration { + type Output = Duration; + + fn add(self, rhs: Duration) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl From for Instant { + fn from(value: Duration) -> Self { + Instant(value.0) + } +} + +static WAKERS: Mutex> = Mutex::new(Vec::new()); + +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug)] +pub struct Instant(u64); impl Instant { - pub fn init() { - assert!(START.lock().is_none()); - let _ = START.lock().insert(std::time::Instant::now()); + const ZERO: Self = Self(0); + + 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 { - let start = channel_read("Instant start not initialized", &START); - Self(start.elapsed()) + NOW.lock().clone().unwrap_or(Instant::ZERO) + } + + 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 for Instant { + fn from(value: u64) -> Self { + Self::from_ticks(value) } } @@ -32,7 +110,7 @@ impl core::ops::Add for Instant { type Output = Instant; fn add(self, rhs: Duration) -> Self::Output { - Self(self.0 + rhs) + Self(self.0 + rhs.0) } } @@ -40,7 +118,7 @@ impl core::ops::Sub for Instant { type Output = Instant; fn sub(self, rhs: Duration) -> Self::Output { - Self(self.0 - rhs) + Self(self.0 - rhs.0) } } @@ -48,104 +126,49 @@ impl core::ops::Sub for Instant { type Output = Duration; fn sub(self, rhs: Instant) -> Self::Output { - self.0 - rhs.0 + Duration(self.0 - rhs.0) } } -fn channel_read(msg: &str, channel: &Mutex>) -> T { - channel.lock().as_ref().expect(msg).clone() -} +static COMPARE: Mutex> = Mutex::new(None); +static TIMER_QUEUE: TimerQueue = TimerQueue::new(); -fn event_write(msg: &str, channel: &Mutex>>, value: T) { - channel.lock().as_ref().expect(msg).send(value).unwrap() -} +pub struct TestMono; -static COMPARE_RX: Mutex>> = Mutex::new(None); -static COMPARE_TX: Mutex>> = Mutex::new(None); -static INTERRUPT_RX: Mutex>> = Mutex::new(None); -static INTERRUPT_TX: Mutex>> = Mutex::new(None); +impl TestMono { + pub fn tick(force_interrupt: bool) -> bool { + let now = Instant::now(); + + 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. - /// - /// 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); - + pub fn init() { 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 - pub fn queue() -> &'static TimerQueue { + pub fn queue() -> &'static TimerQueue { &TIMER_QUEUE } + + pub fn compare() -> Option { + COMPARE.lock().clone() + } } -impl Monotonic for StdTokioMono { - const ZERO: Self::Instant = Instant(Duration::ZERO); +impl Monotonic for TestMono { + const ZERO: Self::Instant = Instant::ZERO; type Instant = Instant; @@ -156,66 +179,108 @@ impl Monotonic for StdTokioMono { } fn set_compare(instant: Self::Instant) { - // TODO: verify that we receive the correct amount & values - // for `set_compare`. - - log::info!("Setting compare to {} ms", instant.0.as_millis()); - - event_write("Compare TX not initialized", &COMPARE_TX, instant); + let _ = COMPARE.lock().insert(instant); } fn clear_compare_flag() {} fn pend_interrupt() { - event_write("Interrupt TX not initialized", &INTERRUPT_TX, ()); + Self::tick(true); } } -static TIMER_QUEUE: TimerQueue = TimerQueue::new(); +#[test] +fn timer_queue() { + TestMono::init(); + let start = Instant::ZERO; -#[tokio::test] -async fn main() { - pretty_env_logger::init(); - - let _interrupt_killer = StdTokioMono::init(); - - let start = std::time::Instant::now(); - - let build_delay_test = |threshold: u128, pre_delay: Option, delay: u64| { - let delay = Duration::from_millis(delay); - let pre_delay = pre_delay.map(Duration::from_millis); + let build_delay_test = |pre_delay: Option, delay: u64| { + let delay = Duration::from_ticks(delay); + let pre_delay = pre_delay.map(Duration::from_ticks); let total = if let Some(pre_delay) = pre_delay { pre_delay + delay } else { delay }; - let total_millis = total.as_millis(); + let total_millis = total.as_ticks(); + async move { 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(); - log::info!("{total_millis} ms delay reached (after {elapsed} ms)"); + let elapsed = start.elapsed().as_ticks(); + println!("{total_millis} ticks delay reached after {elapsed} ticks"); - if elapsed > total_millis.saturating_add(threshold) - || elapsed < total_millis.saturating_sub(threshold) - { - panic!("{total_millis} ms delay was not on time ({elapsed} ms passed instead)"); + if elapsed != total_millis { + panic!( + "{total_millis} ticks delay was not on time ({elapsed} ticks passed instead)" + ); } } }; - // TODO: depending on the precision of the delays that can be used, this threshold - // may have to be altered a bit. - const TIME_THRESHOLD_MS: u128 = 5; + let d1 = build_delay_test(Some(100), 100); + pin_mut!(d1); + let mut d1 = Cassette::new(d1); - let sec1 = build_delay_test(TIME_THRESHOLD_MS, Some(100), 100); - let sec2 = build_delay_test(TIME_THRESHOLD_MS, None, 300); - let sec3 = build_delay_test(TIME_THRESHOLD_MS, None, 400); + let d2 = build_delay_test(None, 300); + pin_mut!(d2); + 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()); } From 53fbf3051458fe249aa51fdeea9c000b719f23ba Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Mon, 10 Apr 2023 13:09:50 +0200 Subject: [PATCH 5/6] Clean up --- rtic-time/tests/timer_queue.rs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/rtic-time/tests/timer_queue.rs b/rtic-time/tests/timer_queue.rs index 7b233740b0..4f285e010a 100644 --- a/rtic-time/tests/timer_queue.rs +++ b/rtic-time/tests/timer_queue.rs @@ -75,19 +75,11 @@ impl Instant { NOW.lock().clone().unwrap_or(Instant::ZERO) } - 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) { + pub async fn wait_until(time: Instant) { core::future::poll_fn(|ctx| { if Instant::now() >= time { Poll::Ready(()) @@ -102,7 +94,7 @@ impl Instant { impl From for Instant { fn from(value: u64) -> Self { - Self::from_ticks(value) + Self(value) } } @@ -206,8 +198,11 @@ fn timer_queue() { let total_millis = total.as_ticks(); async move { + // A `pre_delay` simulates a delay in scheduling, + // without the `pre_delay` being present in the timer + // queue if let Some(pre_delay) = pre_delay { - Instant::sleep_until(start + pre_delay).await; + Instant::wait_until(start + pre_delay).await; } TestMono::queue().delay(delay).await; From 413b985f12b980dd89f892a3b7dc084aa2cbd164 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Mon, 10 Apr 2023 13:39:22 +0200 Subject: [PATCH 6/6] Small cleanup --- rtic-time/tests/timer_queue.rs | 44 ++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/rtic-time/tests/timer_queue.rs b/rtic-time/tests/timer_queue.rs index 4f285e010a..9ad717574f 100644 --- a/rtic-time/tests/timer_queue.rs +++ b/rtic-time/tests/timer_queue.rs @@ -7,7 +7,7 @@ use std::{ task::{Poll, Waker}, }; -use cassette::{pin_mut, Cassette}; +use cassette::Cassette; use parking_lot::Mutex; use rtic_time::{Monotonic, TimerQueue}; @@ -218,39 +218,47 @@ fn timer_queue() { } }; + macro_rules! cassette { + ($($x:ident),* $(,)?) => { $( + // Move the value to ensure that it is owned + let mut $x = $x; + // Shadow the original binding so that it can't be directly accessed + // ever again. + #[allow(unused_mut)] + let mut $x = unsafe { + core::pin::Pin::new_unchecked(&mut $x) + }; + + let mut $x = Cassette::new($x); + )* } + } + let d1 = build_delay_test(Some(100), 100); - pin_mut!(d1); - let mut d1 = Cassette::new(d1); + cassette!(d1); let d2 = build_delay_test(None, 300); - pin_mut!(d2); - let mut d2 = Cassette::new(d2); + cassette!(d2); let d3 = build_delay_test(None, 400); - pin_mut!(d3); - let mut d3 = Cassette::new(d3); + cassette!(d3); - macro_rules! try_poll { - ($fut:ident) => { - if !$fut.is_done() { - $fut.poll_on(); - } + macro_rules! 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); + poll!(d1, d2, 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); + poll!(d1, d2, d3); } if Instant::now() == 0.into() {