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}