impedance/lib.rs
1//! `impedance` is a library that provides utilities to make working with blocking code while in
2//! the context of asynchronous code easier. It is named after [this
3//! phenomenom](https://en.wikipedia.org/wiki/Impedance_matching).
4//!
5//! ## Utilities
6//!
7//! - [`adaptive::AdaptiveFuture`](adaptive::AdaptiveFuture)
8//!
9//! A wrapper around blocking work that can adaptively move that work to another thread when it is
10//! expensive (where expensive == long *wall-time*). It works in tandem with its configuration
11//! mechanism [`Token`](adaptive::Token). This can sometimes give us the best of both worlds:
12//!
13//! ```ignore
14//! $ cargo +nightly bench
15//! ...
16//! test fast_with_adaptive ... bench: 4,782 ns/iter (+/- 627)
17//! test fast_with_adaptive_always_inline ... bench: 4,412 ns/iter (+/- 699)
18//! test fast_with_adaptive_always_spawn ... bench: 55,455 ns/iter (+/- 22,798)
19//! test fast_with_nothing ... bench: 3,391 ns/iter (+/- 227)
20//! test fast_with_spawn_blocking ... bench: 51,054 ns/iter (+/- 10,620)
21//! test slow_with_adaptive ... bench: 12,092,260 ns/iter (+/- 1,572,018)
22//! test slow_with_nothing ... bench: 122,687,873 ns/iter (+/- 16,353,904)
23//! test slow_with_spawn ... bench: 24,730,260 ns/iter (+/- 3,003,759)
24//! test slow_with_spawn_blocking ... bench: 12,543,033 ns/iter (+/- 2,753,322)
25//! ...
26//! ```
27//! (See [the benchmarks
28//! themselves](https://github.com/guswynn/impedance/blob/main/benches/comparisons.rs) for more
29//! info)
30//!
31//! - `buffer_unordered/buffered` helpers (coming hopefully soon)
32//! Helpers that avoid pitfalls when using `buffer_unordered`.
33//!
34//! ## Features
35//! This library should be design in a way such that any executor that has a
36//! `spawn_blocking` method can be used:
37//!
38// TODO(guswynn): can rustdoc auto make these links for me?
39//! - `tokio`: Currently this library tries to provide good support
40//! for [`tokio`](tokio) which is in its `default_features`.
41//! - `async-std-experimental`: This library has experimental support for using [`async-std`](https://docs.rs/async-std) (as well as
42//! [`futures`](https://docs.rs/futures) internally for a oneshot channel). You will need to use `default-features
43//! = false`
44//! and there are caveats: First and foremost, panic payloads's are NOT ALWAYS propagated
45//! correctly, they have a default failed task message when the work was moved to a thread.
46//! - TODO: consider [`async_executors`](https://docs.rs/async_executors) for this abstraction
47pub mod adaptive;
48
49#[cfg(all(feature = "rayon", feature = "tokio"))]
50// TODO(guswynn): use doc_cfg when its stable
51// #[doc(cfg(feature = "signal"))]
52pub mod rayon;
53
54#[cfg(all(test, feature = "tokio"))]
55mod tests {
56 use super::*;
57 use adaptive::{AdaptiveFuture, Token};
58 use tokio::runtime::Handle;
59
60 #[tokio::test]
61 async fn test_basic() {
62 let thing = AdaptiveFuture::new(Token::new(), || 1);
63 assert_eq!(1, thing.await);
64 }
65
66 #[tokio::test]
67 #[should_panic(expected = "Cannot start a runtime from within a runtime")]
68 async fn test_nested() {
69 let thing = AdaptiveFuture::new(Token::new(), || {
70 Handle::current().block_on(async { AdaptiveFuture::new(Token::new(), || 1).await })
71 });
72 assert_eq!(1, thing.await);
73 }
74
75 #[tokio::test]
76 #[should_panic(expected = "Cannot start a runtime from within a runtime")]
77 async fn test_nested_comparison() {
78 let thing = (|| {
79 Handle::current().block_on(async { AdaptiveFuture::new(Token::new(), || 1).await })
80 })();
81 assert_eq!(1, thing);
82 }
83
84 #[tokio::test]
85 #[should_panic(expected = "gus")]
86 async fn test_panic_adaptive() {
87 let thing = AdaptiveFuture::new(Token::new(), || {
88 if false {
89 1_isize
90 } else {
91 panic!("gus");
92 }
93 });
94 assert_eq!(1, thing.await);
95 }
96
97 #[tokio::test]
98 #[should_panic(expected = "gus")]
99 async fn test_panic_spawning() {
100 let thing = AdaptiveFuture::new(Token::always_spawn(), || {
101 if false {
102 1_isize
103 } else {
104 panic!("gus");
105 }
106 });
107 assert_eq!(1, thing.await);
108 }
109}
110
111#[cfg(all(test, feature = "async-std-experimental"))]
112mod async_std_tests {
113 use super::*;
114 use adaptive::{AdaptiveFuture, Token};
115
116 #[async_std::test]
117 async fn test_basic() {
118 let thing = AdaptiveFuture::new(Token::new(), || 1);
119 assert_eq!(1, thing.await);
120 }
121
122 #[async_std::test]
123 #[should_panic(expected = "gus")]
124 async fn test_panic_adaptive() {
125 let thing = AdaptiveFuture::new(Token::new(), || {
126 if false {
127 1_isize
128 } else {
129 panic!("gus");
130 }
131 });
132 assert_eq!(1, thing.await);
133 }
134
135 #[async_std::test]
136 #[should_panic(expected = "task has failed")]
137 async fn test_panic_spawning() {
138 let thing = AdaptiveFuture::new(Token::always_spawn(), || {
139 if false {
140 1_isize
141 } else {
142 panic!("gus");
143 }
144 });
145 assert_eq!(1, thing.await);
146 }
147}