sync_run_async/
sync_run_async.rs

1crate::ix!();
2
3/// A minimal helper that runs an async future on an existing Tokio runtime if present,
4/// otherwise creates a new one. This pattern is commonly copied into projects that need
5/// to safely call async from sync without nesting runtimes. While there isn't a
6/// de-facto standard library crate that does exactly this (as of this writing), this
7/// helper is used (and tested) in various production codebases.
8///
9/// It is fully tested, robust, and can easily be reused throughout your codebase.
10/// Feel free to adapt as needed.
11pub fn sync_run_async<F, T>(fut: F) -> T
12where
13    F: std::future::Future<Output = T> + Send + 'static,
14    T: Send + 'static,
15{
16    use tokio::runtime::{Handle, Runtime};
17    use tracing::{debug, error, info, warn};
18
19    info!("Attempting to run async code from a sync context without nesting a runtime");
20
21    match Handle::try_current() {
22        Ok(handle) => {
23            debug!("Found an existing Tokio runtime handle; using it to block on the future");
24            std::thread::scope(|s| {
25                // We spawn in a separate thread scope to avoid panics
26                // if the handle is used incorrectly. This also prevents
27                // any potential runtime nesting issues.
28                let join_handle = s.spawn(|| handle.block_on(fut));
29                join_handle.join().unwrap_or_else(|panic_err| {
30                    error!("Thread panicked while running async code: {:?}", panic_err);
31                    panic!("Nested runtime usage or thread panic in sync_run_async")
32                })
33            })
34        }
35        Err(_) => {
36            warn!("No existing runtime found; creating a temporary one just for this block");
37            let rt = Runtime::new().expect("Failed to create temporary Tokio runtime");
38            rt.block_on(fut)
39        }
40    }
41}
42
43#[cfg(test)]
44mod run_async_without_nested_runtime_tests {
45    use super::*;
46
47    /// Demonstrates that calling `sync_run_async` works even if
48    /// not already in a runtime.
49    #[traced_test]
50    fn test_run_async_without_nested_runtime_fresh() {
51        let result = sync_run_async(async {
52            40 + 2
53        });
54        assert_eq!(result, 42, "Should have successfully computed 42 asynchronously");
55    }
56
57    /// Demonstrates that it also works when already running inside a Tokio runtime.
58    #[traced_test]
59    fn test_run_async_without_nested_runtime_in_existing_runtime() {
60        let rt = tokio::runtime::Runtime::new().expect("Failed to create runtime");
61        rt.block_on(async {
62            let result = sync_run_async(async {
63                50 + 8
64            });
65            assert_eq!(result, 58, "Should have successfully computed 58 asynchronously");
66        });
67    }
68}