Skip to main content

bark_runtime/
timeout.rs

1use std::time::Duration;
2use std::future::Future;
3
4/// Timeout error.
5///
6/// On native platforms, wraps `tokio::time::error::Elapsed`.
7/// On WASM, uses a custom implementation.
8#[cfg(not(target_arch = "wasm32"))]
9pub use tokio::time::error::Elapsed;
10
11#[cfg(target_arch = "wasm32")]
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct Elapsed;
14
15#[cfg(target_arch = "wasm32")]
16impl std::fmt::Display for Elapsed {
17	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18		f.write_str("deadline has elapsed")
19	}
20}
21
22#[cfg(target_arch = "wasm32")]
23impl std::error::Error for Elapsed {}
24
25/// Timeout a future.
26///
27/// On native platforms, uses `tokio::time::timeout`.
28/// On WASM, uses `futures::future::select`.
29pub async fn timeout<F: Future>(duration: Duration, future: F)
30	-> Result<F::Output, Elapsed>
31{
32	#[cfg(not(target_arch = "wasm32"))]
33	{
34		tokio::time::timeout(duration, future).await
35	}
36
37	#[cfg(target_arch = "wasm32")]
38	{
39		use std::pin::pin;
40		use futures::future::{select, Either};
41
42		let s = pin!(crate::sleep(duration));
43		let f = pin!(future);
44		match select(f, s).await {
45			Either::Left((out, _)) => Ok(out),
46			Either::Right(((), _)) => Err(Elapsed),
47		}
48	}
49}
50
51#[cfg(test)]
52mod test {
53	use super::*;
54
55	#[cfg(target_arch = "wasm32")]
56	use wasm_bindgen_test::wasm_bindgen_test;
57	#[cfg(target_arch = "wasm32")]
58	wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
59
60	/// A future that finishes in time yields its output.
61	#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
62	#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
63	async fn returns_the_output_of_a_fast_future() {
64		let out = timeout(Duration::from_secs(5), async { "done" }).await
65			.expect("an immediate future cannot time out");
66		assert_eq!("done", out);
67	}
68
69	/// A future that sleeps less than the deadline still yields its output.
70	#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
71	#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
72	async fn returns_the_output_of_a_future_that_sleeps_briefly() {
73		let out = timeout(Duration::from_secs(5), async {
74			crate::sleep(Duration::from_millis(10)).await;
75			7
76		}).await.expect("a brief sleep fits well within the deadline");
77		assert_eq!(7, out);
78	}
79
80	/// A future that outlives the deadline is abandoned with an error.
81	#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
82	#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
83	async fn errors_when_the_future_outlives_the_deadline() {
84		timeout(Duration::from_millis(50), async {
85			crate::sleep(Duration::from_secs(60)).await;
86		}).await.expect_err("the deadline should have fired first");
87	}
88
89	/// A zero deadline errors instead of waiting for the future.
90	#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
91	#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
92	async fn errors_on_a_zero_deadline() {
93		timeout(Duration::ZERO, async {
94			crate::sleep(Duration::from_secs(60)).await;
95		}).await.expect_err("a zero deadline is already past");
96	}
97
98	/// The timeout error explains that the deadline passed.
99	#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
100	#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
101	async fn elapsed_error_mentions_the_deadline() {
102		let err = timeout(Duration::from_millis(10), async {
103			crate::sleep(Duration::from_secs(60)).await;
104		}).await.unwrap_err();
105		assert!(err.to_string().contains("elapsed"), "{}", err);
106	}
107}
108