use futures::future;
use tari_shutdown::ShutdownSignal;
use crate::{
ServiceInitializerContext,
context::{ServiceHandles, create_context_notifier_pair},
initializer::{InitializerFn, ServiceInitializationError, ServiceInitializer},
};
pub struct StackBuilder {
initializers: Vec<Box<dyn ServiceInitializer + Send>>,
shutdown_signal: ShutdownSignal,
}
impl StackBuilder {
pub fn new(shutdown_signal: ShutdownSignal) -> Self {
Self {
initializers: Vec::new(),
shutdown_signal,
}
}
}
impl StackBuilder {
pub fn add_initializer<I>(self, initializer: I) -> Self
where I: ServiceInitializer + Send + 'static {
self.add_initializer_boxed(initializer)
}
pub fn add_initializer_fn<TFunc>(self, initializer: TFunc) -> Self
where TFunc: FnOnce(ServiceInitializerContext) -> Result<(), ServiceInitializationError> + Send + 'static {
self.add_initializer_boxed(InitializerFn::new(initializer))
}
pub fn add_initializer_boxed(mut self, initializer: impl ServiceInitializer + Send + 'static) -> Self {
self.initializers.push(Box::new(initializer));
self
}
pub async fn build(self) -> Result<ServiceHandles, ServiceInitializationError> {
let StackBuilder {
shutdown_signal,
mut initializers,
} = self;
let (mut notifier, context) = create_context_notifier_pair(shutdown_signal);
let init_futures = initializers.iter_mut().map(|init| init.initialize(context.clone()));
future::try_join_all(init_futures).await?;
notifier.trigger();
Ok(context.into_inner())
}
}
#[cfg(test)]
mod test {
use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
};
use async_trait::async_trait;
use futures::executor::block_on;
use tari_shutdown::Shutdown;
use tower::service_fn;
use super::*;
#[tokio::test]
async fn service_defn_simple() {
let simple_initializer = |_: ServiceInitializerContext| Ok(());
let shutdown = Shutdown::new();
let handles = StackBuilder::new(shutdown.to_signal())
.add_initializer(simple_initializer)
.build()
.await;
assert!(handles.is_ok());
}
#[derive(Clone)]
struct DummyServiceHandle(usize);
struct DummyInitializer {
state: Arc<AtomicUsize>,
}
impl DummyInitializer {
fn new(state: Arc<AtomicUsize>) -> Self {
Self { state }
}
}
#[async_trait]
impl ServiceInitializer for DummyInitializer {
async fn initialize(&mut self, context: ServiceInitializerContext) -> Result<(), ServiceInitializationError> {
context.register_handle(DummyServiceHandle(123));
context.spawn_when_ready(|handles| async move {
let handle = handles.get_handle::<DummyServiceHandle>().unwrap();
assert_eq!(handle.0, 123);
service_fn(|_: ()| future::ok::<_, ()>(handle.0));
});
self.state.fetch_add(1, Ordering::SeqCst);
Ok(())
}
}
#[tokio::test]
async fn service_stack_new() {
let shared_state = Arc::new(AtomicUsize::new(0));
let shutdown = Shutdown::new();
let initializer = DummyInitializer::new(Arc::clone(&shared_state));
let handles = block_on(
StackBuilder::new(shutdown.to_signal())
.add_initializer(initializer)
.build(),
)
.unwrap();
handles.get_handle::<DummyServiceHandle>().unwrap();
assert_eq!(shared_state.load(Ordering::SeqCst), 1);
}
}