use std::{
any,
any::{Any, TypeId},
collections::HashMap,
sync::{Arc, Mutex},
};
use futures::{Future, FutureExt, future, future::Either};
use tari_shutdown::{Shutdown, ShutdownSignal};
use tokio::task;
use crate::context::LazyService;
pub(crate) fn create_context_notifier_pair(shutdown_signal: ShutdownSignal) -> (Shutdown, ServiceInitializerContext) {
let trigger = Shutdown::new();
let trigger_signal = trigger.to_signal();
(trigger, ServiceInitializerContext::new(shutdown_signal, trigger_signal))
}
#[derive(Clone)]
pub struct ServiceInitializerContext {
inner: ServiceHandles,
ready_signal: ShutdownSignal,
}
impl ServiceInitializerContext {
pub(crate) fn new(shutdown_signal: ShutdownSignal, ready_signal: ShutdownSignal) -> Self {
Self {
inner: ServiceHandles::new(shutdown_signal),
ready_signal,
}
}
pub fn register_handle<H>(&self, handle: H)
where H: Any + Send {
self.inner.register(handle);
}
pub fn lazy_service<F, S>(&self, service_fn: F) -> LazyService<F, Self, S>
where F: FnOnce(ServiceHandles) -> S {
LazyService::new(self.clone(), service_fn)
}
pub fn spawn_when_ready<F, Fut>(self, f: F) -> task::JoinHandle<Fut::Output>
where
F: FnOnce(ServiceHandles) -> Fut + Send + 'static,
Fut: Future + Send + 'static,
Fut::Output: Send,
{
task::spawn(self.wait_ready().then(f))
}
pub fn spawn_until_shutdown<F, Fut>(self, f: F) -> task::JoinHandle<Option<Fut::Output>>
where
F: FnOnce(ServiceHandles) -> Fut + Send + 'static,
Fut: Future + Send + 'static,
Fut::Output: Send + 'static,
{
task::spawn(async move {
let shutdown_signal = self.get_shutdown_signal();
self.ready_signal.await;
let fut = f(self.inner);
futures::pin_mut!(fut);
let either = future::select(shutdown_signal, fut).await;
match either {
Either::Left((_, _)) => None,
Either::Right((res, _)) => Some(res),
}
})
}
pub async fn wait_ready(self) -> ServiceHandles {
self.ready_signal.await;
self.inner
}
pub fn get_shutdown_signal(&self) -> ShutdownSignal {
self.inner.get_shutdown_signal()
}
pub fn into_inner(self) -> ServiceHandles {
self.inner
}
}
macro_rules! acquire_lock {
($e:expr, $m:ident) => {
match $e.$m() {
Ok(lock) => lock,
Err(poisoned) => {
log::warn!(target: "service_framework", "Lock has been POISONED and will be silently recovered");
poisoned.into_inner()
},
}
};
($e:expr) => {
acquire_lock!($e, lock)
};
}
#[derive(Clone)]
pub struct ServiceHandles {
handles: Arc<Mutex<HashMap<TypeId, Box<dyn Any + Send>>>>,
shutdown_signal: ShutdownSignal,
}
impl ServiceHandles {
pub(crate) fn new(shutdown_signal: ShutdownSignal) -> Self {
Self {
handles: Default::default(),
shutdown_signal,
}
}
pub fn register<H>(&self, handle: H)
where H: Any + Send {
acquire_lock!(self.handles).insert(TypeId::of::<H>(), Box::new(handle));
}
pub fn get_handle<H>(&self) -> Option<H>
where H: Clone + 'static {
self.get_handle_by_type_id(TypeId::of::<H>())
}
pub fn take_handle<H: 'static>(&mut self) -> Option<H> {
acquire_lock!(self.handles)
.remove(&TypeId::of::<H>())
.and_then(|handle| handle.downcast::<H>().ok().map(|h| *h))
}
#[track_caller]
pub fn expect_handle<H>(&self) -> H
where H: Clone + 'static {
match self.get_handle_by_type_id(TypeId::of::<H>()) {
Some(h) => h,
None => panic!("Service handle `{}` is not registered", any::type_name::<H>()),
}
}
fn get_handle_by_type_id<H>(&self, type_id: TypeId) -> Option<H>
where H: Clone + 'static {
acquire_lock!(self.handles)
.get(&type_id)
.and_then(|b| b.downcast_ref::<H>())
.cloned()
}
pub fn get_shutdown_signal(&self) -> ShutdownSignal {
self.shutdown_signal.clone()
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn service_handles_insert_get() {
#[derive(Clone)]
struct TestHandle;
let handles = ServiceHandles::new(Shutdown::new().to_signal());
handles.register(TestHandle);
handles.get_handle::<TestHandle>().unwrap();
assert!(handles.get_handle::<()>().is_none());
assert!(handles.get_handle::<usize>().is_none());
}
#[test]
fn insert_get() {
#[derive(Clone)]
struct TestHandle;
let trigger = Shutdown::new();
let context = ServiceInitializerContext::new(trigger.to_signal(), trigger.to_signal());
context.register_handle(TestHandle);
context.inner.expect_handle::<TestHandle>();
assert!(context.inner.get_handle::<()>().is_none());
}
}