Skip to content

Commit 96f99df

Browse files
committed
Change timeout/delay functions to non-async
Because of a compiler bug, the `async` implementations of `delay`/`delay_until`/`timeout`/`timeout_at` produce much larger RAM footprint than they should. Fixes #890.
1 parent fa2a5b4 commit 96f99df

File tree

2 files changed

+103
-98
lines changed

2 files changed

+103
-98
lines changed

rtic-time/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ For each category, *Added*, *Changed*, *Fixed* add new entries at the top!
1111

1212
### Changed
1313

14+
- Replace `async` implementations of `delay`/`delay_until`/`timeout`/`timeout_at` with strucs to reduce memory usage.
15+
1416
### Fixed
1517

1618
- Docs: Rename `DelayUs` to `DelayNs` in docs.

rtic-time/src/lib.rs

Lines changed: 101 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,12 @@
77
#![deny(missing_docs)]
88
#![allow(incomplete_features)]
99

10-
use core::future::{poll_fn, Future};
10+
use core::future::Future;
1111
use core::pin::Pin;
1212
use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
1313
use core::task::{Poll, Waker};
14-
use futures_util::{
15-
future::{select, Either},
16-
pin_mut,
17-
};
1814
use linked_list::{Link, LinkedList};
1915
pub use monotonic::Monotonic;
20-
use rtic_common::dropper::OnDrop;
2116

2217
pub mod half_period_counter;
2318
mod linked_list;
@@ -75,26 +70,6 @@ pub struct TimerQueue<Mono: Monotonic> {
7570
/// This indicates that there was a timeout.
7671
pub struct TimeoutError;
7772

78-
/// This is needed to make the async closure in `delay_until` accept that we "share"
79-
/// the link possible between threads.
80-
struct LinkPtr<Mono: Monotonic>(*mut Option<linked_list::Link<WaitingWaker<Mono>>>);
81-
82-
impl<Mono: Monotonic> Clone for LinkPtr<Mono> {
83-
fn clone(&self) -> Self {
84-
LinkPtr(self.0)
85-
}
86-
}
87-
88-
impl<Mono: Monotonic> LinkPtr<Mono> {
89-
/// This will dereference the pointer stored within and give out an `&mut`.
90-
unsafe fn get(&mut self) -> &mut Option<linked_list::Link<WaitingWaker<Mono>>> {
91-
&mut *self.0
92-
}
93-
}
94-
95-
unsafe impl<Mono: Monotonic> Send for LinkPtr<Mono> {}
96-
unsafe impl<Mono: Monotonic> Sync for LinkPtr<Mono> {}
97-
9873
impl<Mono: Monotonic> TimerQueue<Mono> {
9974
/// Make a new queue.
10075
pub const fn new() -> Self {
@@ -166,29 +141,25 @@ impl<Mono: Monotonic> TimerQueue<Mono> {
166141
}
167142

168143
/// Timeout at a specific time.
169-
pub async fn timeout_at<F: Future>(
170-
&self,
171-
instant: Mono::Instant,
172-
future: F,
173-
) -> Result<F::Output, TimeoutError> {
174-
let delay = self.delay_until(instant);
175-
176-
pin_mut!(future);
177-
pin_mut!(delay);
178-
179-
match select(future, delay).await {
180-
Either::Left((r, _)) => Ok(r),
181-
Either::Right(_) => Err(TimeoutError),
144+
pub fn timeout_at<F: Future>(&self, instant: Mono::Instant, future: F) -> Timeout<'_, Mono, F> {
145+
Timeout {
146+
delay: Delay::<Mono> {
147+
instant,
148+
queue: &self.queue,
149+
link_ptr: None,
150+
marker: AtomicUsize::new(0),
151+
},
152+
future,
182153
}
183154
}
184155

185156
/// Timeout after at least a specific duration.
186157
#[inline]
187-
pub async fn timeout_after<F: Future>(
158+
pub fn timeout_after<F: Future>(
188159
&self,
189160
duration: Mono::Duration,
190161
future: F,
191-
) -> Result<F::Output, TimeoutError> {
162+
) -> Timeout<'_, Mono, F> {
192163
let now = Mono::now();
193164
let mut timeout = now + duration;
194165
if now != timeout {
@@ -197,12 +168,12 @@ impl<Mono: Monotonic> TimerQueue<Mono> {
197168

198169
// Wait for one period longer, because by definition timers have an uncertainty
199170
// of one period, so waiting for 'at least' needs to compensate for that.
200-
self.timeout_at(timeout, future).await
171+
self.timeout_at(timeout, future)
201172
}
202173

203174
/// Delay for at least some duration of time.
204175
#[inline]
205-
pub async fn delay(&self, duration: Mono::Duration) {
176+
pub fn delay(&self, duration: Mono::Duration) -> Delay<'_, Mono> {
206177
let now = Mono::now();
207178
let mut timeout = now + duration;
208179
if now != timeout {
@@ -211,79 +182,111 @@ impl<Mono: Monotonic> TimerQueue<Mono> {
211182

212183
// Wait for one period longer, because by definition timers have an uncertainty
213184
// of one period, so waiting for 'at least' needs to compensate for that.
214-
self.delay_until(timeout).await;
185+
self.delay_until(timeout)
215186
}
216187

217188
/// Delay to some specific time instant.
218-
pub async fn delay_until(&self, instant: Mono::Instant) {
189+
pub fn delay_until(&self, instant: Mono::Instant) -> Delay<'_, Mono> {
219190
if !self.initialized.load(Ordering::Relaxed) {
220191
panic!(
221192
"The timer queue is not initialized with a monotonic, you need to run `initialize`"
222193
);
223194
}
195+
Delay::<Mono> {
196+
instant,
197+
queue: &self.queue,
198+
link_ptr: None,
199+
marker: AtomicUsize::new(0),
200+
}
201+
}
202+
}
224203

225-
let mut link_ptr: Option<linked_list::Link<WaitingWaker<Mono>>> = None;
204+
/// Future returned by `delay` and `delay_until`.
205+
pub struct Delay<'q, Mono: Monotonic> {
206+
instant: Mono::Instant,
207+
queue: &'q LinkedList<WaitingWaker<Mono>>,
208+
link_ptr: Option<linked_list::Link<WaitingWaker<Mono>>>,
209+
marker: AtomicUsize,
210+
}
226211

227-
// Make this future `Drop`-safe
228-
// SAFETY(link_ptr): Shadow the original definition of `link_ptr` so we can't abuse it.
229-
let mut link_ptr =
230-
LinkPtr(&mut link_ptr as *mut Option<linked_list::Link<WaitingWaker<Mono>>>);
231-
let mut link_ptr2 = link_ptr.clone();
212+
impl<'q, Mono: Monotonic> Future for Delay<'q, Mono> {
213+
type Output = ();
232214

233-
let queue = &self.queue;
234-
let marker = &AtomicUsize::new(0);
215+
fn poll(self: Pin<&mut Self>, cx: &mut core::task::Context<'_>) -> Poll<Self::Output> {
216+
// SAFETY: We ensure we never move anything out of this.
217+
let this = unsafe { self.get_unchecked_mut() };
235218

236-
let dropper = OnDrop::new(|| {
237-
queue.delete(marker.load(Ordering::Relaxed));
238-
});
219+
if Mono::now() >= this.instant {
220+
return Poll::Ready(());
221+
}
239222

240-
poll_fn(|cx| {
241-
if Mono::now() >= instant {
242-
return Poll::Ready(());
223+
// SAFETY: this is dereferenced only here and in `drop`. As the queue deletion is done only
224+
// in `drop` we can't do this access concurrently with queue removal.
225+
let link = &mut this.link_ptr;
226+
if link.is_none() {
227+
let link_ref = link.insert(Link::new(WaitingWaker {
228+
waker: cx.waker().clone(),
229+
release_at: this.instant,
230+
was_popped: AtomicBool::new(false),
231+
}));
232+
233+
// SAFETY(new_unchecked): The address to the link is stable as it is defined
234+
// outside this stack frame.
235+
// SAFETY(insert): `link_ref` lfetime comes from `link_ptr` which itself is owned by
236+
// the `Delay` struct. The `Delay::drop` impl ensures that the link is removed from the
237+
// queue on drop, which happens before the struct and thus `link_ptr` goes out of
238+
// scope.
239+
let (head_updated, addr) = unsafe { this.queue.insert(Pin::new_unchecked(link_ref)) };
240+
this.marker.store(addr, Ordering::Relaxed);
241+
if head_updated {
242+
Mono::pend_interrupt()
243243
}
244+
}
244245

245-
// SAFETY: This pointer is only dereferenced here and on drop of the future
246-
// which happens outside this `poll_fn`'s stack frame, so this mutable access cannot
247-
// happen at the same time as `dropper` runs.
248-
let link = unsafe { link_ptr2.get() };
249-
if link.is_none() {
250-
let link_ref = link.insert(Link::new(WaitingWaker {
251-
waker: cx.waker().clone(),
252-
release_at: instant,
253-
was_popped: AtomicBool::new(false),
254-
}));
255-
256-
// SAFETY(new_unchecked): The address to the link is stable as it is defined
257-
//outside this stack frame.
258-
// SAFETY(insert): `link_ref` lifetime comes from `link_ptr` that is shadowed, and
259-
// we make sure in `dropper` that the link is removed from the queue before
260-
// dropping `link_ptr` AND `dropper` makes sure that the shadowed `link_ptr` lives
261-
// until the end of the stack frame.
262-
let (head_updated, addr) = unsafe { queue.insert(Pin::new_unchecked(link_ref)) };
263-
264-
marker.store(addr, Ordering::Relaxed);
265-
266-
if head_updated {
267-
// Pend the monotonic handler if the queue head was updated.
268-
Mono::pend_interrupt()
269-
}
246+
Poll::Pending
247+
}
248+
}
249+
250+
impl<'q, Mono: Monotonic> Drop for Delay<'q, Mono> {
251+
fn drop(&mut self) {
252+
// SAFETY: The dropper is cannot be run at the same time as poll, so we can't end up
253+
// derefencing this concurrently to the one in `poll`.
254+
match self.link_ptr.as_ref() {
255+
None => return,
256+
// If it was popped from the queue there is no need to run delete
257+
Some(link) if link.val.was_popped.load(Ordering::Relaxed) => return,
258+
_ => {}
259+
}
260+
self.queue.delete(self.marker.load(Ordering::Relaxed));
261+
}
262+
}
263+
264+
/// Future returned by `timeout` and `timeout_at`.
265+
pub struct Timeout<'q, Mono: Monotonic, F> {
266+
delay: Delay<'q, Mono>,
267+
future: F,
268+
}
269+
270+
impl<'q, Mono: Monotonic, F: Future> Future for Timeout<'q, Mono, F> {
271+
type Output = Result<F::Output, TimeoutError>;
272+
273+
fn poll(self: Pin<&mut Self>, cx: &mut core::task::Context<'_>) -> Poll<Self::Output> {
274+
let inner = unsafe { self.get_unchecked_mut() };
275+
276+
{
277+
let f = unsafe { Pin::new_unchecked(&mut inner.future) };
278+
if let Poll::Ready(v) = f.poll(cx) {
279+
return Poll::Ready(Ok(v));
270280
}
281+
}
271282

272-
Poll::Pending
273-
})
274-
.await;
275-
276-
// SAFETY: We only run this and dereference the pointer if we have
277-
// exited the `poll_fn` below in the `drop(dropper)` call. The other dereference
278-
// of this pointer is in the `poll_fn`.
279-
if let Some(link) = unsafe { link_ptr.get() } {
280-
if link.val.was_popped.load(Ordering::Relaxed) {
281-
// If it was popped from the queue there is no need to run delete
282-
dropper.defuse();
283+
{
284+
let d = unsafe { Pin::new_unchecked(&mut inner.delay) };
285+
if d.poll(cx).is_ready() {
286+
return Poll::Ready(Err(TimeoutError));
283287
}
284-
} else {
285-
// Make sure that our link is deleted from the list before we drop this stack
286-
drop(dropper);
287288
}
289+
290+
Poll::Pending
288291
}
289292
}

0 commit comments

Comments
 (0)