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}