sync_run_async/
sync_run_async.rs

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