Skip to main content

agnostic_lite/
embassy.rs

1use core::{
2  future::Future,
3  pin::Pin,
4  task::{Context, Poll},
5};
6
7use embassy_executor::{SendSpawner, Spawner};
8use embassy_sync::once_lock::OnceLock;
9use futures_channel::oneshot;
10use futures_util::future::{Either, select};
11
12use crate::{
13  AsyncBlockingSpawner, AsyncLocalSpawner, AsyncSpawner, Yielder, spawner::handle::JoinError,
14};
15
16mod after;
17mod delay;
18mod interval;
19mod sleep;
20mod timeout;
21
22pub use after::*;
23pub use delay::*;
24pub use interval::*;
25pub use sleep::*;
26pub use timeout::*;
27
28/// The maximum number of [`spawn`](crate::RuntimeLite::spawn)ed tasks that can be alive at the
29/// same time.
30///
31/// Because [`embassy-executor`](https://docs.rs/embassy-executor) allocates its task storage
32/// statically, the number of concurrently running spawned tasks is bounded. If more than
33/// `TASK_POOL_SIZE` tasks are alive at once, [`spawn`](crate::RuntimeLite::spawn) fails and the
34/// returned [`JoinHandle`] resolves to an error.
35pub const TASK_POOL_SIZE: usize = 64;
36
37type DynFuture = Pin<std::boxed::Box<dyn Future<Output = ()> + Send + 'static>>;
38
39#[embassy_executor::task(pool_size = TASK_POOL_SIZE)]
40async fn task_runner(fut: DynFuture) {
41  fut.await
42}
43
44static SPAWNER: OnceLock<SendSpawner> = OnceLock::new();
45
46/// Initializes the global [`embassy-executor`](https://docs.rs/embassy-executor) spawner used by
47/// [`EmbassyRuntime`].
48///
49/// This **must** be called once, from within a running embassy executor (i.e. with the
50/// [`Spawner`] handed to you by `#[embassy_executor::main]` or `Executor::run`), before any task
51/// is spawned through [`EmbassyRuntime`]. Spawning before initialization will panic.
52///
53/// Calling this more than once is a no-op; only the first spawner is retained.
54///
55/// ```rust,ignore
56/// #[embassy_executor::main]
57/// async fn main(spawner: embassy_executor::Spawner) {
58///   agnostic_lite::embassy::init(spawner);
59///   // `EmbassyRuntime::spawn(..)` is now usable from anywhere.
60/// }
61/// ```
62pub fn init(spawner: Spawner) {
63  let _ = SPAWNER.init(spawner.make_send());
64}
65
66#[inline]
67fn global_spawner() -> SendSpawner {
68  *SPAWNER.try_get().expect(
69    "embassy runtime is not initialized; call `agnostic_lite::embassy::init(spawner)` from within \
70     a running embassy executor before spawning",
71  )
72}
73
74fn spawn_send<F>(future: F) -> JoinHandle<F::Output>
75where
76  F: Future + Send + 'static,
77  F::Output: Send + 'static,
78{
79  let (tx, rx) = oneshot::channel::<F::Output>();
80  let (stop_tx, stop_rx) = oneshot::channel::<bool>();
81
82  let wrapped: DynFuture = std::boxed::Box::pin(async move {
83    let future = core::pin::pin!(future);
84    match select(future, stop_rx).await {
85      Either::Left((output, _)) => {
86        let _ = tx.send(output);
87      }
88      Either::Right((signal, future)) => match signal {
89        // `abort` was called: drop the task without running it to completion.
90        Ok(true) => {}
91        // `detach` was called, or the handle was dropped: keep running to completion.
92        Ok(false) | Err(_) => {
93          let _ = future.await;
94        }
95      },
96    }
97  });
98
99  // On `SpawnError::Busy` the task pool is exhausted; `wrapped` (and therefore `tx`) is dropped,
100  // so `rx` resolves to a `JoinError` and the caller observes the failure through the handle.
101  if let Ok(token) = task_runner(wrapped) {
102    global_spawner().spawn(token);
103  }
104
105  JoinHandle { stop_tx, rx }
106}
107
108/// The [`JoinHandle`](crate::JoinHandle) returned by [`EmbassySpawner`].
109///
110/// Dropping the handle detaches the task, letting it keep running in the background.
111pub struct JoinHandle<T> {
112  stop_tx: oneshot::Sender<bool>,
113  rx: oneshot::Receiver<T>,
114}
115
116impl<T> JoinHandle<T> {
117  /// Detaches the task, letting it keep running in the background.
118  #[inline]
119  pub fn detach(self) {
120    let _ = self.stop_tx.send(false);
121  }
122
123  /// Aborts the task. If it has not finished yet, it will not be run to completion.
124  #[inline]
125  pub fn abort(self) {
126    let _ = self.stop_tx.send(true);
127  }
128}
129
130impl<T> Future for JoinHandle<T> {
131  type Output = Result<T, JoinError>;
132
133  fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
134    Pin::new(&mut self.rx)
135      .poll(cx)
136      .map(|res| res.map_err(|_| JoinError::new()))
137  }
138}
139
140impl<O> crate::JoinHandle<O> for JoinHandle<O> {
141  type JoinError = JoinError;
142
143  fn abort(self) {
144    Self::abort(self)
145  }
146
147  fn detach(self) {
148    Self::detach(self)
149  }
150}
151
152impl<O> crate::LocalJoinHandle<O> for JoinHandle<O> {
153  type JoinError = JoinError;
154
155  fn detach(self) {
156    Self::detach(self)
157  }
158}
159
160/// Future for the [`yield_now`](RuntimeLite::yield_now) function.
161///
162/// [`RuntimeLite::yield_now`]: crate::RuntimeLite::yield_now
163#[derive(Debug)]
164#[must_use = "futures do nothing unless you `.await` or poll them"]
165struct YieldNow(bool);
166
167impl Future for YieldNow {
168  type Output = ();
169
170  fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
171    if !self.0 {
172      self.0 = true;
173      cx.waker().wake_by_ref();
174      Poll::Pending
175    } else {
176      Poll::Ready(())
177    }
178  }
179}
180
181/// A [`AsyncSpawner`] that uses the [`embassy-executor`](https://docs.rs/embassy-executor) runtime.
182#[derive(Debug, Clone, Copy)]
183pub struct EmbassySpawner;
184
185impl Yielder for EmbassySpawner {
186  async fn yield_now() {
187    YieldNow(false).await
188  }
189
190  async fn yield_now_local() {
191    YieldNow(false).await
192  }
193}
194
195impl AsyncSpawner for EmbassySpawner {
196  type JoinHandle<F>
197    = JoinHandle<F>
198  where
199    F: Send + 'static;
200
201  fn spawn<F>(future: F) -> Self::JoinHandle<F::Output>
202  where
203    F::Output: Send + 'static,
204    F: Future + Send + 'static,
205  {
206    spawn_send(future)
207  }
208}
209
210impl AsyncLocalSpawner for EmbassySpawner {
211  type JoinHandle<F>
212    = JoinHandle<F>
213  where
214    F: 'static;
215
216  fn spawn_local<F>(_future: F) -> Self::JoinHandle<F::Output>
217  where
218    F::Output: 'static,
219    F: Future + 'static,
220  {
221    panic!(
222      "the embassy backend cannot spawn `!Send` local tasks: its spawner is stored in a global \
223       `static`, which can only hold the `Send` spawner. Use `spawn` with a `Send` future instead."
224    )
225  }
226}
227
228impl AsyncBlockingSpawner for EmbassySpawner {
229  type JoinHandle<R>
230    = JoinHandle<R>
231  where
232    R: Send + 'static;
233
234  fn spawn_blocking<F, R>(_f: F) -> Self::JoinHandle<R>
235  where
236    F: FnOnce() -> R + Send + 'static,
237    R: Send + 'static,
238  {
239    panic!("the embassy backend does not support blocking tasks")
240  }
241}
242
243/// Runs the given future to completion on the current thread by busy-polling it.
244///
245/// This uses [`embassy_futures::block_on`], which repeatedly polls the future without sleeping the
246/// CPU. It is provided for parity and host testing; on real embedded targets you almost always
247/// want to drive futures with an [`embassy_executor`] executor instead.
248pub fn block_on<F: Future>(future: F) -> F::Output {
249  embassy_futures::block_on(future)
250}
251
252#[inline]
253pub(crate) fn to_embassy_duration(d: core::time::Duration) -> embassy_time::Duration {
254  embassy_time::Duration::from_micros(d.as_micros().min(u64::MAX as u128) as u64)
255}
256
257#[inline]
258pub(crate) fn from_embassy_duration(d: embassy_time::Duration) -> core::time::Duration {
259  core::time::Duration::from_micros(d.as_micros())
260}
261
262/// A measurement of a monotonically nondecreasing clock, backed by [`embassy_time::Instant`].
263///
264/// Sub-microsecond precision is truncated when converting to and from [`core::time::Duration`].
265#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
266pub struct Instant(embassy_time::Instant);
267
268impl core::hash::Hash for Instant {
269  #[inline]
270  fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
271    self.0.as_ticks().hash(state);
272  }
273}
274
275impl Instant {
276  /// Returns an instant corresponding to "now".
277  #[inline]
278  #[must_use]
279  pub fn now() -> Self {
280    Self(embassy_time::Instant::now())
281  }
282
283  /// Returns the underlying [`embassy_time::Instant`].
284  #[inline]
285  pub const fn into_embassy(self) -> embassy_time::Instant {
286    self.0
287  }
288}
289
290impl From<embassy_time::Instant> for Instant {
291  #[inline]
292  fn from(instant: embassy_time::Instant) -> Self {
293    Self(instant)
294  }
295}
296
297impl From<Instant> for embassy_time::Instant {
298  #[inline]
299  fn from(instant: Instant) -> Self {
300    instant.0
301  }
302}
303
304impl core::ops::Add<core::time::Duration> for Instant {
305  type Output = Self;
306
307  #[inline]
308  fn add(self, rhs: core::time::Duration) -> Self {
309    Self(self.0 + to_embassy_duration(rhs))
310  }
311}
312
313impl core::ops::AddAssign<core::time::Duration> for Instant {
314  #[inline]
315  fn add_assign(&mut self, rhs: core::time::Duration) {
316    self.0 = self.0 + to_embassy_duration(rhs);
317  }
318}
319
320impl core::ops::Sub<core::time::Duration> for Instant {
321  type Output = Self;
322
323  #[inline]
324  fn sub(self, rhs: core::time::Duration) -> Self {
325    Self(self.0 - to_embassy_duration(rhs))
326  }
327}
328
329impl core::ops::SubAssign<core::time::Duration> for Instant {
330  #[inline]
331  fn sub_assign(&mut self, rhs: core::time::Duration) {
332    self.0 = self.0 - to_embassy_duration(rhs);
333  }
334}
335
336impl core::ops::Sub<Instant> for Instant {
337  type Output = core::time::Duration;
338
339  #[inline]
340  fn sub(self, rhs: Instant) -> core::time::Duration {
341    from_embassy_duration(self.0 - rhs.0)
342  }
343}
344
345impl crate::time::Instant for Instant {
346  #[inline]
347  fn now() -> Self {
348    Self(embassy_time::Instant::now())
349  }
350
351  #[inline]
352  fn elapsed(&self) -> core::time::Duration {
353    from_embassy_duration(self.0.elapsed())
354  }
355
356  #[inline]
357  fn checked_add(&self, duration: core::time::Duration) -> Option<Self> {
358    self.0.checked_add(to_embassy_duration(duration)).map(Self)
359  }
360
361  #[inline]
362  fn checked_sub(&self, duration: core::time::Duration) -> Option<Self> {
363    self.0.checked_sub(to_embassy_duration(duration)).map(Self)
364  }
365
366  #[inline]
367  fn checked_duration_since(&self, earlier: Self) -> Option<core::time::Duration> {
368    self
369      .0
370      .checked_duration_since(earlier.0)
371      .map(from_embassy_duration)
372  }
373}
374
375// The `Instant` trait requires conversions to and from `std::time::Instant` when the `std` feature
376// is enabled. The embassy clock and the std clock are unrelated, so these conversions anchor the
377// embassy timeline to the std timeline at "now". They are only meaningful for host interop and
378// testing; on real `no_std` targets these impls do not exist.
379#[cfg(feature = "std")]
380const _: () = {
381  use std::time::Instant as StdInstant;
382
383  impl From<StdInstant> for Instant {
384    fn from(value: StdInstant) -> Self {
385      let now_std = StdInstant::now();
386      let now = embassy_time::Instant::now();
387      Self(if value >= now_std {
388        now + to_embassy_duration(value - now_std)
389      } else {
390        now
391          .checked_sub(to_embassy_duration(now_std - value))
392          .unwrap_or(now)
393      })
394    }
395  }
396
397  impl From<Instant> for StdInstant {
398    fn from(value: Instant) -> Self {
399      let now = embassy_time::Instant::now();
400      let now_std = StdInstant::now();
401      if value.0 >= now {
402        now_std + from_embassy_duration(value.0 - now)
403      } else {
404        now_std
405          .checked_sub(from_embassy_duration(now - value.0))
406          .unwrap_or(now_std)
407      }
408    }
409  }
410};
411
412/// Concrete [`RuntimeLite`](crate::RuntimeLite) implementation based on
413/// [`embassy-executor`](https://docs.rs/embassy-executor).
414///
415/// Before any task is spawned through this runtime, the global spawner must be installed once with
416/// [`init`] from within a running embassy executor. See the [module documentation](self) for the
417/// caveats of this backend ([`block_on`] busy-polls, [`spawn_blocking`] and local spawning panic,
418/// and the number of live spawned tasks is bounded by [`TASK_POOL_SIZE`]).
419///
420/// [`spawn_blocking`]: crate::LocalRuntimeLite::spawn_blocking
421#[derive(Debug, Clone, Copy)]
422pub struct EmbassyRuntime;
423
424impl core::fmt::Display for EmbassyRuntime {
425  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
426    write!(f, "embassy")
427  }
428}
429
430impl crate::LocalRuntimeLite for EmbassyRuntime {
431  type LocalSpawner = EmbassySpawner;
432  type BlockingSpawner = EmbassySpawner;
433
434  type Instant = Instant;
435
436  type LocalInterval = EmbassyInterval;
437
438  type LocalSleep = EmbassySleep;
439
440  type LocalDelay<F>
441    = EmbassyDelay<F>
442  where
443    F: Future;
444
445  type LocalTimeout<F>
446    = EmbassyTimeout<F>
447  where
448    F: Future;
449
450  fn new() -> Self {
451    Self
452  }
453
454  fn name() -> &'static str {
455    "embassy"
456  }
457
458  fn fqname() -> &'static str {
459    "embassy-executor"
460  }
461
462  fn block_on<F: Future>(f: F) -> F::Output {
463    block_on(f)
464  }
465
466  fn interval_local(interval: core::time::Duration) -> Self::LocalInterval {
467    use crate::time::AsyncIntervalExt;
468
469    EmbassyInterval::interval(interval)
470  }
471
472  fn interval_local_at(start: Instant, period: core::time::Duration) -> Self::LocalInterval {
473    use crate::time::AsyncIntervalExt;
474
475    EmbassyInterval::interval_at(start, period)
476  }
477
478  fn sleep_local(duration: core::time::Duration) -> Self::LocalSleep {
479    use crate::time::AsyncSleepExt;
480
481    EmbassySleep::sleep(duration)
482  }
483
484  fn sleep_local_until(instant: Instant) -> Self::LocalSleep {
485    use crate::time::AsyncSleepExt;
486
487    EmbassySleep::sleep_until(instant)
488  }
489
490  fn delay_local<F>(duration: core::time::Duration, fut: F) -> Self::LocalDelay<F>
491  where
492    F: Future,
493  {
494    use crate::time::AsyncLocalDelayExt;
495
496    <EmbassyDelay<F> as AsyncLocalDelayExt<F>>::delay(duration, fut)
497  }
498
499  fn delay_local_at<F>(deadline: Instant, fut: F) -> Self::LocalDelay<F>
500  where
501    F: Future,
502  {
503    use crate::time::AsyncLocalDelayExt;
504
505    <EmbassyDelay<F> as AsyncLocalDelayExt<F>>::delay_at(deadline, fut)
506  }
507
508  fn timeout_local<F>(duration: core::time::Duration, future: F) -> Self::LocalTimeout<F>
509  where
510    F: Future,
511  {
512    use crate::time::AsyncLocalTimeout;
513
514    <EmbassyTimeout<F> as AsyncLocalTimeout<F>>::timeout_local(duration, future)
515  }
516
517  fn timeout_local_at<F>(deadline: Instant, future: F) -> Self::LocalTimeout<F>
518  where
519    F: Future,
520  {
521    use crate::time::AsyncLocalTimeout;
522
523    <EmbassyTimeout<F> as AsyncLocalTimeout<F>>::timeout_local_at(deadline, future)
524  }
525}
526
527impl crate::RuntimeLite for EmbassyRuntime {
528  type Spawner = EmbassySpawner;
529
530  type AfterSpawner = EmbassySpawner;
531
532  type Interval = EmbassyInterval;
533
534  type Sleep = EmbassySleep;
535
536  type Delay<F>
537    = EmbassyDelay<F>
538  where
539    F: Future + Send;
540
541  type Timeout<F>
542    = EmbassyTimeout<F>
543  where
544    F: Future + Send;
545
546  async fn yield_now() {
547    YieldNow(false).await
548  }
549
550  fn interval(interval: core::time::Duration) -> Self::Interval {
551    use crate::time::AsyncIntervalExt;
552
553    EmbassyInterval::interval(interval)
554  }
555
556  fn interval_at(start: Instant, period: core::time::Duration) -> Self::Interval {
557    use crate::time::AsyncIntervalExt;
558
559    EmbassyInterval::interval_at(start, period)
560  }
561
562  fn sleep(duration: core::time::Duration) -> Self::Sleep {
563    use crate::time::AsyncSleepExt;
564
565    EmbassySleep::sleep(duration)
566  }
567
568  fn sleep_until(instant: Instant) -> Self::Sleep {
569    use crate::time::AsyncSleepExt;
570
571    EmbassySleep::sleep_until(instant)
572  }
573
574  fn delay<F>(duration: core::time::Duration, fut: F) -> Self::Delay<F>
575  where
576    F: Future + Send,
577  {
578    use crate::time::AsyncDelayExt;
579
580    <EmbassyDelay<F> as AsyncDelayExt<F>>::delay(duration, fut)
581  }
582
583  fn delay_at<F>(deadline: Instant, fut: F) -> Self::Delay<F>
584  where
585    F: Future + Send,
586  {
587    use crate::time::AsyncDelayExt;
588
589    <EmbassyDelay<F> as AsyncDelayExt<F>>::delay_at(deadline, fut)
590  }
591
592  fn timeout<F>(duration: core::time::Duration, future: F) -> Self::Timeout<F>
593  where
594    F: Future + Send,
595  {
596    use crate::time::AsyncTimeout;
597
598    <EmbassyTimeout<F> as AsyncTimeout<F>>::timeout(duration, future)
599  }
600
601  fn timeout_at<F>(deadline: Instant, future: F) -> Self::Timeout<F>
602  where
603    F: Future + Send,
604  {
605    use crate::time::AsyncTimeout;
606
607    <EmbassyTimeout<F> as AsyncTimeout<F>>::timeout_at(deadline, future)
608  }
609}
610
611#[cfg(test)]
612mod tests {
613  use super::*;
614  use crate::{LocalRuntimeLite, RuntimeLite, time::Instant as _};
615  use core::{
616    sync::atomic::{AtomicBool, Ordering},
617    time::Duration,
618  };
619  use futures::executor::block_on;
620  use futures_util::StreamExt;
621
622  // Embassy's `Executor::run` never returns and the global spawner is set once, so all tests share
623  // a single executor running on a background thread. Test futures are driven on the test thread
624  // with `futures::executor::block_on` (embassy-time's generic queue, enabled for the test build,
625  // lets `Timer` work under any waker); tasks spawned through `EmbassyRuntime` run on the
626  // background executor and are bridged back via the channel-backed join handles.
627  fn ensure_runtime() {
628    static START: std::sync::Once = std::sync::Once::new();
629    START.call_once(|| {
630      let (tx, rx) = std::sync::mpsc::channel();
631      std::thread::Builder::new()
632        .name("embassy-test-executor".into())
633        .spawn(move || {
634          let executor: &'static mut embassy_executor::Executor =
635            std::boxed::Box::leak(std::boxed::Box::new(embassy_executor::Executor::new()));
636          executor.run(|spawner| {
637            init(spawner);
638            let _ = tx.send(());
639          });
640        })
641        .unwrap();
642      rx.recv().unwrap();
643    });
644  }
645
646  #[test]
647  fn spawn_and_join() {
648    ensure_runtime();
649    assert_eq!(
650      block_on(async { EmbassyRuntime::spawn(async { 21 * 2 }).await.unwrap() }),
651      42
652    );
653  }
654
655  #[test]
656  fn spawn_detach_runs() {
657    ensure_runtime();
658    let flag = std::sync::Arc::new(AtomicBool::new(false));
659    let f = flag.clone();
660    EmbassyRuntime::spawn_detach(async move { f.store(true, Ordering::SeqCst) });
661    block_on(EmbassyRuntime::sleep(Duration::from_millis(100)));
662    assert!(flag.load(Ordering::SeqCst));
663  }
664
665  #[test]
666  fn block_on_drives_to_completion() {
667    let out = EmbassyRuntime::block_on(async {
668      EmbassyRuntime::yield_now().await;
669      42
670    });
671    assert_eq!(out, 42);
672  }
673
674  #[test]
675  fn sleep_waits() {
676    block_on(async {
677      let start = EmbassyRuntime::now();
678      EmbassyRuntime::sleep(Duration::from_millis(50)).await;
679      assert!(start.elapsed() >= Duration::from_millis(40));
680    });
681  }
682
683  #[test]
684  fn timeout_expires_and_completes() {
685    block_on(async {
686      let expired = EmbassyRuntime::timeout(Duration::from_millis(20), async {
687        EmbassyRuntime::sleep(Duration::from_millis(500)).await;
688      })
689      .await;
690      assert!(expired.is_err());
691
692      let done = EmbassyRuntime::timeout(Duration::from_millis(200), async { 7 }).await;
693      assert_eq!(done.unwrap(), 7);
694    });
695  }
696
697  #[test]
698  fn interval_first_tick_is_immediate() {
699    block_on(async {
700      let start = EmbassyRuntime::now();
701      let mut interval = EmbassyRuntime::interval(Duration::from_millis(50));
702      interval.next().await;
703      assert!(start.elapsed() < Duration::from_millis(40));
704      interval.next().await;
705      assert!(start.elapsed() >= Duration::from_millis(40));
706    });
707  }
708
709  #[test]
710  fn delay_runs_after_duration() {
711    block_on(async {
712      let start = EmbassyRuntime::now();
713      let out = EmbassyRuntime::delay(Duration::from_millis(50), async { 9 })
714        .await
715        .unwrap();
716      assert_eq!(out, 9);
717      assert!(start.elapsed() >= Duration::from_millis(40));
718    });
719  }
720
721  #[test]
722  fn spawn_after() {
723    ensure_runtime();
724    block_on(crate::tests::spawn_after_unittest::<EmbassyRuntime>());
725  }
726
727  #[test]
728  fn spawn_after_cancel() {
729    ensure_runtime();
730    block_on(crate::tests::spawn_after_cancel_unittest::<EmbassyRuntime>());
731  }
732
733  #[test]
734  fn spawn_after_drop() {
735    ensure_runtime();
736    block_on(crate::tests::spawn_after_drop_unittest::<EmbassyRuntime>());
737  }
738
739  #[test]
740  fn spawn_after_abort() {
741    ensure_runtime();
742    block_on(crate::tests::spawn_after_abort_unittest::<EmbassyRuntime>());
743  }
744
745  #[test]
746  fn spawn_after_reset_to_pass() {
747    ensure_runtime();
748    block_on(crate::tests::spawn_after_reset_to_pass_unittest::<
749      EmbassyRuntime,
750    >());
751  }
752
753  #[test]
754  fn spawn_after_reset_to_future() {
755    ensure_runtime();
756    block_on(crate::tests::spawn_after_reset_to_future_unittest::<
757      EmbassyRuntime,
758    >());
759  }
760}