use std::error::Error; use async_trait::async_trait; use crate::{ resource::{unwrap_constructed, Resource}, slot::SlotDesc, state::Aerosol, ConstructibleResource, }; /// Implemented for resources which can be constructed asynchronously from other /// resources. Requires feature `async`. #[async_trait] pub trait AsyncConstructibleResource: Resource { /// Error type for when resource fails to be constructed. type Error: Error + Send + Sync; /// Construct the resource with the provided application state. async fn construct_async(aero: &Aerosol) -> Result; } #[async_trait] impl AsyncConstructibleResource for T { type Error = ::Error; async fn construct_async(aero: &Aerosol) -> Result { Self::construct(aero) } } impl Aerosol { /// Try to get or construct an instance of `T` asynchronously. Requires feature `async`. pub async fn try_obtain_async(&self) -> Result { match self.try_get_slot() { Some(SlotDesc::Filled(x)) => Ok(x), Some(SlotDesc::Placeholder) | None => match self.wait_for_slot_async::(true).await { Some(x) => Ok(x), None => match T::construct_async(self).await { Ok(x) => { self.fill_placeholder::(x.clone()); Ok(x) } Err(e) => { self.clear_placeholder::(); Err(e) } }, }, } } /// Get or construct an instance of `T` asynchronously. Panics if unable. Requires feature `async`. pub async fn obtain_async(&self) -> T { unwrap_constructed(self.try_obtain_async::().await) } } #[cfg(test)] mod tests { use std::{convert::Infallible, time::Duration}; use super::*; #[derive(Debug, Clone)] struct Dummy; #[async_trait] impl AsyncConstructibleResource for Dummy { type Error = Infallible; async fn construct_async(_app_state: &Aerosol) -> Result { tokio::time::sleep(Duration::from_millis(100)).await; Ok(Self) } } #[tokio::test] async fn obtain() { let state = Aerosol::new(); state.obtain_async::().await; } #[tokio::test] async fn obtain_race() { let state = Aerosol::new(); let mut handles = Vec::new(); for _ in 0..100 { let state = state.clone(); handles.push(tokio::spawn(async move { state.obtain_async::().await; })); } for handle in handles { handle.await.unwrap(); } } #[derive(Debug, Clone)] struct DummyRecursive; #[async_trait] impl AsyncConstructibleResource for DummyRecursive { type Error = Infallible; async fn construct_async(aero: &Aerosol) -> Result { aero.obtain_async::().await; Ok(Self) } } #[tokio::test] async fn obtain_recursive() { let state = Aerosol::new(); state.obtain_async::().await; } #[tokio::test] async fn obtain_recursive_race() { let state = Aerosol::new(); let mut handles = Vec::new(); for _ in 0..100 { let state = state.clone(); handles.push(tokio::spawn(async move { state.obtain_async::().await; })); } } #[derive(Debug, Clone)] struct DummyCyclic; #[async_trait] impl AsyncConstructibleResource for DummyCyclic { type Error = Infallible; async fn construct_async(aero: &Aerosol) -> Result { aero.obtain_async::().await; Ok(Self) } } #[tokio::test] #[should_panic(expected = "Cycle detected")] async fn obtain_cyclic() { let state = Aerosol::new(); state.obtain_async::().await; } #[derive(Debug, Clone)] struct DummySync; impl ConstructibleResource for DummySync { type Error = Infallible; fn construct(_app_state: &Aerosol) -> Result { std::thread::sleep(Duration::from_millis(100)); Ok(Self) } } #[derive(Debug, Clone)] struct DummySyncRecursive; #[async_trait] impl AsyncConstructibleResource for DummySyncRecursive { type Error = Infallible; async fn construct_async(aero: &Aerosol) -> Result { aero.obtain_async::().await; Ok(Self) } } #[tokio::test] async fn obtain_sync_recursive() { let state = Aerosol::new(); state.obtain_async::().await; } #[tokio::test] async fn obtain_sync_recursive_race() { let state = Aerosol::new(); let mut handles = Vec::new(); for _ in 0..100 { let state = state.clone(); handles.push(tokio::spawn(async move { state.obtain_async::().await; })); } } }