Skip to main content

agnostic_lite/
lib.rs

1#![doc = include_str!("../README.md")]
2#![cfg_attr(not(feature = "std"), no_std)]
3#![forbid(unsafe_code)]
4#![deny(warnings, missing_docs)]
5#![cfg_attr(docsrs, feature(doc_cfg))]
6#![cfg_attr(docsrs, allow(unused_attributes))]
7
8#[cfg(all(feature = "alloc", not(feature = "std")))]
9extern crate alloc as std;
10
11#[cfg(feature = "std")]
12extern crate std;
13
14macro_rules! cfg_time_with_docsrs {
15  ($($item:item)*) => {
16    $(
17      #[cfg(feature = "time")]
18      #[cfg_attr(docsrs, doc(cfg(feature = "time")))]
19      $item
20    )*
21  };
22}
23
24macro_rules! cfg_time {
25  ($($item:item)*) => {
26    $(
27      #[cfg(feature = "time")]
28      $item
29    )*
30  };
31}
32
33use core::future::Future;
34
35cfg_time_with_docsrs!(
36  /// Time related traits
37  pub mod time;
38);
39
40/// Macro to conditionally compile items for `tokio` feature
41#[macro_export]
42macro_rules! cfg_tokio {
43  ($($item:item)*) => {
44    $(
45      #[cfg(feature = "tokio")]
46      #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
47      $item
48    )*
49  };
50  (@no_doc_cfg $($item:item)*) => {
51    $(
52      #[cfg(feature = "tokio")]
53      $item
54    )*
55  };
56}
57
58/// Macro to conditionally compile items for `smol` feature
59#[macro_export]
60macro_rules! cfg_smol {
61  ($($item:item)*) => {
62    $(
63      #[cfg(feature = "smol")]
64      #[cfg_attr(docsrs, doc(cfg(feature = "smol")))]
65      $item
66    )*
67  };
68  (@no_doc_cfg $($item:item)*) => {
69    $(
70      #[cfg(feature = "smol")]
71      $item
72    )*
73  };
74}
75
76/// Macro to conditionally compile items for `unix` system
77#[macro_export]
78macro_rules! cfg_unix {
79  ($($item:item)*) => {
80    $(
81      #[cfg(feature = "unix")]
82      #[cfg_attr(docsrs, doc(cfg(feature = "unix")))]
83      $item
84    )*
85  };
86  (@no_doc_cfg $($item:item)*) => {
87    $(
88      #[cfg(feature = "unix")]
89      $item
90    )*
91  };
92}
93
94/// Macro to conditionally compile items for `windows` system
95#[macro_export]
96macro_rules! cfg_windows {
97  ($($item:item)*) => {
98    $(
99      #[cfg(feature = "windows")]
100      #[cfg_attr(docsrs, doc(cfg(feature = "windows")))]
101      $item
102    )*
103  };
104  (@no_doc_cfg $($item:item)*) => {
105    $(
106      #[cfg(feature = "windows")]
107      $item
108    )*
109  };
110}
111
112/// Macro to conditionally compile items for `linux` system
113#[macro_export]
114macro_rules! cfg_linux {
115  ($($item:item)*) => {
116    $(
117      #[cfg(target_os = "linux")]
118      #[cfg_attr(docsrs, doc(cfg(target_os = "linux")))]
119      $item
120    )*
121  };
122  (@no_doc_cfg $($item:item)*) => {
123    $(
124      #[cfg(target_os = "linux")]
125      $item
126    )*
127  };
128}
129
130#[macro_use]
131mod spawner;
132
133/// Concrete runtime implementations based on [`tokio`] runtime.
134///
135/// [`tokio`]: https://docs.rs/tokio
136#[cfg(feature = "tokio")]
137#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
138pub mod tokio;
139
140/// Concrete runtime implementations based on [`smol`] runtime.
141///
142/// [`smol`]: https://docs.rs/smol
143#[cfg(feature = "smol")]
144#[cfg_attr(docsrs, doc(cfg(feature = "smol")))]
145pub mod smol;
146
147/// Concrete runtime implementations based on [`wasm-bindgen-futures`].
148///
149/// [`wasm-bindgen-futures`]: https://docs.rs/wasm-bindgen-futures
150#[cfg(feature = "wasm")]
151#[cfg_attr(docsrs, doc(cfg(feature = "wasm")))]
152pub mod wasm;
153
154/// Concrete runtime implementations based on the [`embassy-executor`] runtime.
155///
156/// This is the only `no_std` runtime backend. It requires `alloc` (futures are boxed) and a
157/// running [`embassy-executor`] executor. Because [`embassy-executor`] allocates task storage
158/// statically and spawns through a `Send` spawner, this backend is necessarily a degraded fit for
159/// the [`RuntimeLite`] contract:
160///
161/// - You **must** call [`embassy::init`] once, from within a running executor, to install the
162///   global spawner before spawning anything.
163/// - The number of concurrently-alive spawned tasks is bounded by [`embassy::TASK_POOL_SIZE`];
164///   exceeding it makes the returned handle resolve to an error.
165/// - [`block_on`](embassy::block_on) busy-polls (it does not sleep the CPU).
166/// - [`spawn_blocking`](LocalRuntimeLite::spawn_blocking) and local spawning
167///   ([`spawn_local`](LocalRuntimeLite::spawn_local)) **panic**: the global spawner is `Send`-only, so
168///   `!Send` local tasks cannot be spawned through it.
169///
170/// [`embassy-executor`]: https://docs.rs/embassy-executor
171#[cfg(feature = "embassy")]
172#[cfg_attr(docsrs, doc(cfg(feature = "embassy")))]
173pub mod embassy;
174
175/// Time related traits concrete implementations for runtime based on [`async-io`](::async_io), e.g. [`smol`].
176///
177/// [`smol`]: https://docs.rs/smol
178#[cfg(feature = "async-io")]
179#[cfg_attr(docsrs, doc(cfg(feature = "async-io")))]
180pub mod async_io;
181
182pub use spawner::*;
183
184/// Yielder hints the runtime to execution back
185pub trait Yielder {
186  /// Yields execution back to the runtime.
187  fn yield_now() -> impl Future<Output = ()> + Send;
188
189  /// Yields execution back to the runtime.
190  fn yield_now_local() -> impl Future<Output = ()>;
191}
192
193/// The **thread-pinned half** of a runtime: construction, `block_on`, local and
194/// blocking spawning, and the `!Send`-tolerant time family.
195///
196/// This is everything a consumer needs to host futures on the **current
197/// thread**. A thread-pinned host — a `LocalSet`-shaped executor, or any
198/// runtime whose timers and join handles are deliberately `!Send` — can
199/// implement this trait even though it can never satisfy [`RuntimeLite`]'s
200/// `Send` family; that family lives on [`RuntimeLite`], the extension of this
201/// trait.
202///
203/// Deliberately **out of scope**: completion-based (proactor) runtimes such
204/// as `compio`. Their I/O model wants a native driver integration of its own,
205/// not a reactor-shaped runtime abstraction wrapped around it — this crate
206/// does not target them, and the split does not promise them.
207///
208/// The marker type itself is still `Send + Sync + Copy`: it is a zero-sized
209/// tag naming the runtime, not a value of it, so it stays thread-mobile even
210/// when everything it spawns is pinned.
211pub trait LocalRuntimeLite: Sized + Unpin + Copy + Send + Sync + 'static {
212  /// The local spawner type for this runtime
213  ///
214  /// Note: an implementation may **panic** when the current thread has no
215  /// local-executor context to target — see [`AsyncLocalSpawner`]'s contract
216  /// note for the per-runtime behavior.
217  type LocalSpawner: AsyncLocalSpawner;
218  /// The blocking spawner type for this runtime
219  type BlockingSpawner: AsyncBlockingSpawner;
220
221  cfg_time_with_docsrs!(
222    /// The instant type for this runtime
223    type Instant: time::Instant;
224
225    /// The local interval type for this runtime
226    type LocalInterval: time::AsyncLocalInterval<Instant = Self::Instant>;
227
228    /// The local sleep type for this runtime
229    type LocalSleep: time::AsyncLocalSleep<Instant = Self::Instant>;
230
231    /// The local delay type for this runtime
232    type LocalDelay<F>: time::AsyncLocalDelay<F, Instant = Self::Instant>
233    where
234      F: Future;
235
236    /// The local timeout type for this runtime
237    type LocalTimeout<F>: time::AsyncLocalTimeout<F, Instant = Self::Instant>
238    where
239      F: Future;
240  );
241
242  /// Create a new instance of the runtime
243  fn new() -> Self;
244
245  /// Returns the name of the runtime
246  ///
247  /// See also fully qualified name of the runtime
248  fn name() -> &'static str;
249
250  /// Returns the fully qualified name of the runtime
251  ///
252  /// See also [`name`](LocalRuntimeLite::name) of the runtime
253  fn fqname() -> &'static str;
254
255  /// Spawn a future onto the local runtime
256  fn spawn_local<F>(future: F) -> <Self::LocalSpawner as AsyncLocalSpawner>::JoinHandle<F::Output>
257  where
258    F: Future + 'static,
259    F::Output: 'static,
260  {
261    <Self::LocalSpawner as AsyncLocalSpawner>::spawn_local(future)
262  }
263
264  /// Spawn a future onto the local runtime and detach it
265  fn spawn_local_detach<F>(future: F)
266  where
267    F: Future + 'static,
268    F::Output: 'static,
269  {
270    <Self::LocalSpawner as AsyncLocalSpawner>::spawn_local_detach(future)
271  }
272
273  /// Spawn a blocking function onto the runtime
274  fn spawn_blocking<F, R>(f: F) -> <Self::BlockingSpawner as AsyncBlockingSpawner>::JoinHandle<R>
275  where
276    F: FnOnce() -> R + Send + 'static,
277    R: Send + 'static,
278  {
279    <Self::BlockingSpawner as AsyncBlockingSpawner>::spawn_blocking(f)
280  }
281
282  /// Spawn a blocking function onto the runtime and detach it
283  fn spawn_blocking_detach<F, R>(f: F)
284  where
285    F: FnOnce() -> R + Send + 'static,
286    R: Send + 'static,
287  {
288    <Self::BlockingSpawner as AsyncBlockingSpawner>::spawn_blocking_detach(f);
289  }
290
291  /// Block the current thread on the given future
292  fn block_on<F: Future>(f: F) -> F::Output;
293
294  cfg_time_with_docsrs!(
295    /// Returns an instant corresponding to "now".
296    fn now() -> Self::Instant {
297      <Self::Instant as time::Instant>::now()
298    }
299
300    /// Create a new interval that starts at the current time and
301    /// yields every `period` duration
302    fn interval_local(interval: core::time::Duration) -> Self::LocalInterval;
303
304    /// Create a new interval that starts at the given instant and
305    /// yields every `period` duration
306    fn interval_local_at(start: Self::Instant, period: core::time::Duration)
307    -> Self::LocalInterval;
308
309    /// Create a new sleep future that completes after the given duration
310    /// has elapsed
311    fn sleep_local(duration: core::time::Duration) -> Self::LocalSleep;
312
313    /// Create a new sleep future that completes at the given instant
314    /// has elapsed
315    fn sleep_local_until(instant: Self::Instant) -> Self::LocalSleep;
316
317    /// Like [`delay`](RuntimeLite::delay), but does not require the `fut` to be `Send`.
318    /// Create a new delay future that runs the `fut` after the given duration
319    /// has elapsed. The `Future` will never be polled until the duration has
320    /// elapsed.
321    ///
322    /// The behavior of this function may different in different runtime implementations.
323    fn delay_local<F>(duration: core::time::Duration, fut: F) -> Self::LocalDelay<F>
324    where
325      F: Future;
326
327    /// Like [`delay_at`](RuntimeLite::delay_at), but does not require the `fut` to be `Send`.
328    /// Create a new timeout future that runs the `future` after the given deadline
329    /// The `Future` will never be polled until the deadline has reached.
330    ///
331    /// The behavior of this function may different in different runtime implementations.
332    fn delay_local_at<F>(deadline: Self::Instant, fut: F) -> Self::LocalDelay<F>
333    where
334      F: Future;
335
336    /// Like [`timeout`](RuntimeLite::timeout), but does not requrie the `future` to be `Send`.
337    /// Requires a `Future` to complete before the specified duration has elapsed.
338    ///
339    /// The behavior of this function may different in different runtime implementations.
340    fn timeout_local<F>(duration: core::time::Duration, future: F) -> Self::LocalTimeout<F>
341    where
342      F: Future;
343
344    /// Like [`timeout_at`](RuntimeLite::timeout_at), but does not requrie the `future` to be `Send`.
345    /// Requires a `Future` to complete before the specified duration has elapsed.
346    ///
347    /// The behavior of this function may different in different runtime implementations.
348    fn timeout_local_at<F>(deadline: Self::Instant, future: F) -> Self::LocalTimeout<F>
349    where
350      F: Future;
351  );
352}
353
354/// Runtime trait: the **`Send` extension** of [`LocalRuntimeLite`].
355///
356/// Split in 0.7: the thread-pinned half — construction, `block_on`, local and
357/// blocking spawning, and the `Local*` time family — lives on
358/// [`LocalRuntimeLite`]; this trait adds the multithread-spawnable family
359/// (`Send` futures, `Send` timers, the after-spawner).
360///
361/// # 0.7 source compatibility
362///
363/// **Generic** consumers are unaffected: with an `R: RuntimeLite` bound, every
364/// former item still resolves through the same `R::` paths via the supertrait.
365/// Two invocation forms ARE source-breaking and need a one-line migration:
366///
367/// - a **concrete-type** call of a moved member (`SmolRuntime::block_on(..)`)
368///   needs [`LocalRuntimeLite`] in scope — supertrait items do not come into
369///   scope by importing the subtrait;
370/// - a **UFCS** call through this trait (`<R as RuntimeLite>::name()`) must
371///   name the trait that now owns the member
372///   (`<R as LocalRuntimeLite>::name()`).
373///
374/// Implementors provide the two impl blocks separately. A runtime that can
375/// only pin work to the current thread implements [`LocalRuntimeLite`] alone.
376pub trait RuntimeLite: LocalRuntimeLite {
377  /// The spawner type for this runtime
378  type Spawner: AsyncSpawner;
379
380  cfg_time_with_docsrs!(
381    /// The after spawner type for this runtime
382    type AfterSpawner: AsyncAfterSpawner<Instant = Self::Instant>;
383
384    /// The interval type for this runtime
385    type Interval: time::AsyncInterval<Instant = Self::Instant>;
386
387    /// The sleep type for this runtime
388    type Sleep: time::AsyncSleep<Instant = Self::Instant>;
389
390    /// The delay type for this runtime
391    type Delay<F>: time::AsyncDelay<F, Instant = Self::Instant>
392    where
393      F: Future + Send;
394
395    /// The timeout type for this runtime
396    type Timeout<F>: time::AsyncTimeout<F, Instant = Self::Instant>
397    where
398      F: Future + Send;
399  );
400
401  /// Spawn a future onto the runtime
402  fn spawn<F>(future: F) -> <Self::Spawner as AsyncSpawner>::JoinHandle<F::Output>
403  where
404    F::Output: Send + 'static,
405    F: Future + Send + 'static,
406  {
407    <Self::Spawner as AsyncSpawner>::spawn(future)
408  }
409
410  /// Spawn a future onto the runtime and detach it
411  fn spawn_detach<F>(future: F)
412  where
413    F::Output: Send + 'static,
414    F: Future + Send + 'static,
415  {
416    <Self::Spawner as AsyncSpawner>::spawn_detach(future);
417  }
418
419  /// Yield the current task
420  fn yield_now() -> impl Future<Output = ()> + Send;
421
422  cfg_time_with_docsrs!(
423    /// Spawn a future onto the runtime and run the given future after the given duration
424    fn spawn_after<F>(
425      duration: core::time::Duration,
426      future: F,
427    ) -> <Self::AfterSpawner as AsyncAfterSpawner>::JoinHandle<F::Output>
428    where
429      F::Output: Send + 'static,
430      F: Future + Send + 'static,
431    {
432      <Self::AfterSpawner as AsyncAfterSpawner>::spawn_after(duration, future)
433    }
434
435    /// Spawn a future onto the runtime and run the given future after the given instant.
436    fn spawn_after_at<F>(
437      at: Self::Instant,
438      future: F,
439    ) -> <Self::AfterSpawner as AsyncAfterSpawner>::JoinHandle<F::Output>
440    where
441      F::Output: Send + 'static,
442      F: Future + Send + 'static,
443    {
444      <Self::AfterSpawner as AsyncAfterSpawner>::spawn_after_at(at, future)
445    }
446
447    /// Create a new interval that starts at the current time and
448    /// yields every `period` duration
449    fn interval(interval: core::time::Duration) -> Self::Interval;
450
451    /// Create a new interval that starts at the given instant and
452    /// yields every `period` duration
453    fn interval_at(start: Self::Instant, period: core::time::Duration) -> Self::Interval;
454
455    /// Create a new sleep future that completes after the given duration
456    /// has elapsed
457    fn sleep(duration: core::time::Duration) -> Self::Sleep;
458
459    /// Create a new sleep future that completes at the given instant
460    /// has elapsed
461    fn sleep_until(instant: Self::Instant) -> Self::Sleep;
462
463    /// Create a new delay future that runs the `fut` after the given duration
464    /// has elapsed. The `Future` will never be polled until the duration has
465    /// elapsed.
466    ///
467    /// The behavior of this function may different in different runtime implementations.
468    fn delay<F>(duration: core::time::Duration, fut: F) -> Self::Delay<F>
469    where
470      F: Future + Send;
471
472    /// Create a new timeout future that runs the `future` after the given deadline.
473    /// The `Future` will never be polled until the deadline has reached.
474    ///
475    /// The behavior of this function may different in different runtime implementations.
476    fn delay_at<F>(deadline: Self::Instant, fut: F) -> Self::Delay<F>
477    where
478      F: Future + Send;
479
480    /// Requires a `Future` to complete before the specified duration has elapsed.
481    ///
482    /// The behavior of this function may different in different runtime implementations.
483    fn timeout<F>(duration: core::time::Duration, future: F) -> Self::Timeout<F>
484    where
485      F: Future + Send;
486
487    /// Requires a `Future` to complete before the specified instant in time.
488    ///
489    /// The behavior of this function may different in different runtime implementations.
490    fn timeout_at<F>(deadline: Self::Instant, future: F) -> Self::Timeout<F>
491    where
492      F: Future + Send;
493  );
494}
495
496/// Unit test for the [`RuntimeLite`]
497///
498/// These helpers exercise the time-related runtime methods, so they require the `time` feature.
499#[cfg(all(any(test, feature = "test"), feature = "std", feature = "time"))]
500#[cfg_attr(docsrs, doc(cfg(all(any(test, feature = "test"), feature = "time"))))]
501pub mod tests {
502  use core::sync::atomic::{AtomicUsize, Ordering};
503
504  use std::{sync::Arc, time::Duration};
505
506  use super::{AfterHandle, RuntimeLite};
507
508  /// Unit test for the [`RuntimeLite::spawn_after`] function
509  pub async fn spawn_after_unittest<R: RuntimeLite>() {
510    let ctr = Arc::new(AtomicUsize::new(1));
511    let ctr1 = ctr.clone();
512    let handle = R::spawn_after(Duration::from_secs(1), async move {
513      ctr1.fetch_add(1, Ordering::SeqCst);
514    });
515
516    R::sleep(Duration::from_millis(500)).await;
517    assert_eq!(ctr.load(Ordering::SeqCst), 1);
518
519    handle.await.unwrap();
520    assert_eq!(ctr.load(Ordering::SeqCst), 2);
521  }
522
523  /// Unit test for the [`RuntimeLite::spawn_after`] function
524  ///
525  /// The task will be canceled before it completes
526  pub async fn spawn_after_cancel_unittest<R: RuntimeLite>() {
527    let ctr = Arc::new(AtomicUsize::new(1));
528    let ctr1 = ctr.clone();
529    let handle = R::spawn_after(Duration::from_secs(1), async move {
530      ctr1.fetch_add(1, Ordering::SeqCst);
531    });
532
533    R::sleep(Duration::from_millis(500)).await;
534    assert_eq!(ctr.load(Ordering::SeqCst), 1);
535
536    let o = handle.cancel().await;
537    assert!(o.is_none());
538    assert_eq!(ctr.load(Ordering::SeqCst), 1);
539  }
540
541  /// Unit test for the [`RuntimeLite::spawn_after`] function
542  ///
543  /// The [`AfterHandle`] will be dropped immediately after it is created
544  pub async fn spawn_after_drop_unittest<R: RuntimeLite>() {
545    let ctr = Arc::new(AtomicUsize::new(1));
546    let ctr1 = ctr.clone();
547    drop(R::spawn_after(Duration::from_secs(1), async move {
548      ctr1.fetch_add(1, Ordering::SeqCst);
549    }));
550
551    R::sleep(Duration::from_millis(500)).await;
552    assert_eq!(ctr.load(Ordering::SeqCst), 1);
553
554    R::sleep(Duration::from_millis(600)).await;
555    assert_eq!(ctr.load(Ordering::SeqCst), 2);
556  }
557
558  /// Unit test for the [`RuntimeLite::spawn_after`] function
559  ///
560  /// The [`AfterHandle`] will be abort after it is created, and the task will not be executed.
561  pub async fn spawn_after_abort_unittest<R: RuntimeLite>() {
562    let ctr = Arc::new(AtomicUsize::new(1));
563    let ctr1 = ctr.clone();
564    let handle = R::spawn_after(Duration::from_secs(1), async move {
565      ctr1.fetch_add(1, Ordering::SeqCst);
566    });
567
568    R::sleep(Duration::from_millis(500)).await;
569    assert_eq!(ctr.load(Ordering::SeqCst), 1);
570
571    handle.abort();
572    R::sleep(Duration::from_millis(600)).await;
573    assert_eq!(ctr.load(Ordering::SeqCst), 1);
574  }
575
576  /// Unit test for the [`RuntimeLite::spawn_after`] function
577  ///
578  /// The [`AfterHandle`] will be reset to passed than the original duration after it is created, and the task will be executed after the reset duration.
579  pub async fn spawn_after_reset_to_pass_unittest<R: RuntimeLite>() {
580    let ctr = Arc::new(AtomicUsize::new(1));
581    let ctr1 = ctr.clone();
582    let handle = R::spawn_after(Duration::from_secs(1), async move {
583      ctr1.fetch_add(1, Ordering::SeqCst);
584    });
585
586    R::sleep(Duration::from_millis(500)).await;
587    assert_eq!(ctr.load(Ordering::SeqCst), 1);
588
589    handle.reset(Duration::from_millis(250));
590    R::sleep(Duration::from_millis(10)).await;
591    assert_eq!(ctr.load(Ordering::SeqCst), 2);
592  }
593
594  /// Unit test for the [`RuntimeLite::spawn_after`] function
595  ///
596  /// The [`AfterHandle`] will be reset to future than the original duration after it is created, and the task will be executed after the reset duration.
597  pub async fn spawn_after_reset_to_future_unittest<R: RuntimeLite>() {
598    let ctr = Arc::new(AtomicUsize::new(1));
599    let ctr1 = ctr.clone();
600    let handle = R::spawn_after(Duration::from_secs(1), async move {
601      ctr1.fetch_add(1, Ordering::SeqCst);
602    });
603
604    R::sleep(Duration::from_millis(500)).await;
605    assert_eq!(ctr.load(Ordering::SeqCst), 1);
606
607    handle.reset(Duration::from_millis(1250)); // now delay 1.25s
608    R::sleep(Duration::from_millis(750 + 10)).await; // we already delayed 500ms, so remaining is 750ms
609    assert_eq!(ctr.load(Ordering::SeqCst), 2);
610  }
611}