use std::{error::Error, sync::Arc}; use async_trait::async_trait; use crate::{ resource::{unwrap_constructed, Resource}, slot::SlotDesc, state::Aerosol, sync_constructible::Constructible, }; /// Implemented for values which can be constructed asynchronously from other /// resources. Requires feature `async`. #[async_trait] pub trait AsyncConstructible: Sized { /// 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 AsyncConstructible for T { type Error = ::Error; async fn construct_async(aero: &Aerosol) -> Result { Self::construct(aero) } } /// Automatically implemented for values which can be indirectly asynchronously constructed from other resources. /// Requires feature `async`. #[async_trait] pub trait IndirectlyAsyncConstructible: Sized { /// 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 IndirectlyAsyncConstructible for T { type Error = T::Error; async fn construct_async(aero: &Aerosol) -> Result { T::construct_async(aero).await } } macro_rules! impl_async_constructible { (<$t:ident>; $($x:ty: $y:expr;)*) => { $( #[async_trait] impl<$t: IndirectlyAsyncConstructible> IndirectlyAsyncConstructible for $x { type Error = $t::Error; async fn construct_async(aero: &Aerosol) -> Result { $t::construct_async(aero).await.map($y) } } )* }; } impl_async_constructible! { ; Arc: Arc::new; std::sync::Mutex: std::sync::Mutex::new; parking_lot::Mutex: parking_lot::Mutex::new; std::sync::RwLock: std::sync::RwLock::new; parking_lot::RwLock: parking_lot::RwLock::new; } /// Implemented for resources which can be asynchronously constructed from other resources. Requires feature `async`. pub trait AsyncConstructibleResource: Resource + IndirectlyAsyncConstructible {} impl AsyncConstructibleResource for T {} 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) } /// Try to initialize an instance of `T` asynchronously. Does nothing if `T` is already initialized. pub async fn try_init_async(&self) -> Result<(), T::Error> { match self.wait_for_slot_async::(true).await { Some(_) => Ok(()), None => match T::construct_async(self).await { Ok(x) => { self.fill_placeholder::(x); Ok(()) } Err(e) => { self.clear_placeholder::(); Err(e) } }, } } /// Initialize an instance of `T` asynchronously. Does nothing if `T` is already initialized. Panics if unable. pub async fn init_async(&self) { unwrap_constructed::(self.try_init_async::().await) } } #[cfg(test)] mod tests { use std::{convert::Infallible, time::Duration}; use super::*; #[derive(Debug, Clone)] struct Dummy; #[async_trait] impl AsyncConstructible 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 AsyncConstructible 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 AsyncConstructible 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 Constructible 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 AsyncConstructible 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; })); } } #[derive(Debug)] struct DummyNonClone; #[async_trait] impl AsyncConstructible for DummyNonClone { 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_non_clone() { let state = Aerosol::new(); state.obtain_async::>().await; } }