use std::{ any::Any, collections::BTreeMap, fmt::Write, ops::Deref, sync::{Arc, OnceLock, RwLock, Weak}, }; use async_trait::async_trait; use conduwuit::{err, error::inspect_log, utils::string::SplitInfallible, Err, Result, Server}; use database::Database; /// Abstract interface for a Service #[async_trait] pub(crate) trait Service: Any + Send + Sync { /// Implement the construction of the service instance. Services are /// generally singletons so expect this to only be called once for a /// service type. Note that it may be called again after a server reload, /// but the prior instance will have been dropped first. Failure will /// shutdown the server with an error. fn build(args: Args<'_>) -> Result> where Self: Sized; /// Implement the service's worker loop. The service manager spawns a /// task and calls this function after all services have been built. async fn worker(self: Arc) -> Result<()> { Ok(()) } /// Interrupt the service. This is sent to initiate a graceful shutdown. /// The service worker should return from its work loop. fn interrupt(&self) {} /// Clear any caches or similar runtime state. fn clear_cache(&self) {} /// Memory usage report in a markdown string. fn memory_usage(&self, _out: &mut dyn Write) -> Result<()> { Ok(()) } /// Return the name of the service. /// i.e. `crate::service::make_name(std::module_path!())` fn name(&self) -> &str; } /// Args are passed to `Service::build` when a service is constructed. This /// allows for arguments to change with limited impact to the many services. pub(crate) struct Args<'a> { pub(crate) server: &'a Arc, pub(crate) db: &'a Arc, pub(crate) service: &'a Arc, } /// Dep is a reference to a service used within another service. /// Circular-dependencies between services require this indirection. pub(crate) struct Dep { dep: OnceLock>, service: Weak, name: &'static str, } pub(crate) type Map = RwLock; pub(crate) type MapType = BTreeMap; pub(crate) type MapVal = (Weak, Weak); pub(crate) type MapKey = String; /// SAFETY: Workaround for a compiler limitation (or bug) where it is Hard to /// prove the Sync'ness of Dep because services contain circular references /// to other services through Dep's. The Sync'ness of Dep can still be /// proved without unsafety by declaring the crate-attribute #![recursion_limit /// = "192"] but this may take a while. Re-evaluate this when a new trait-solver /// (such as Chalk) becomes available. unsafe impl Sync for Dep {} /// SAFETY: Ancillary to unsafe impl Sync; while this is not needed to prevent /// violating the recursion_limit, the trait-solver still spends an inordinate /// amount of time to prove this. unsafe impl Send for Dep {} impl Deref for Dep { type Target = Arc; /// Dereference a dependency. The dependency must be ready or panics. #[inline] fn deref(&self) -> &Self::Target { self.dep.get_or_init( #[inline(never)] || self.init(), ) } } impl Dep { #[inline] fn init(&self) -> Arc { let service = self .service .upgrade() .expect("services map exists for dependency initialization."); require::(&service, self.name) } } impl<'a> Args<'a> { /// Create a lazy-reference to a service when constructing another Service. #[inline] pub(crate) fn depend(&'a self, name: &'static str) -> Dep { Dep:: { dep: OnceLock::new(), service: Arc::downgrade(self.service), name, } } /// Create a reference immediately to a service when constructing another /// Service. The other service must be constructed. #[inline] pub(crate) fn require(&'a self, name: &str) -> Arc { require::(self.service, name) } } /// Reference a Service by name. Panics if the Service does not exist or was /// incorrectly cast. #[inline] fn require(map: &Map, name: &str) -> Arc { try_get::(map, name) .inspect_err(inspect_log) .expect("Failure to reference service required by another service.") } /// Reference a Service by name. Returns None if the Service does not exist, but /// panics if incorrectly cast. /// /// # Panics /// Incorrect type is not a silent failure (None) as the type never has a reason /// to be incorrect. pub(crate) fn get(map: &Map, name: &str) -> Option> where T: Any + Send + Sync + Sized, { map.read() .expect("locked for reading") .get(name) .map(|(_, s)| { s.upgrade().map(|s| { s.downcast::() .expect("Service must be correctly downcast.") }) })? } /// Reference a Service by name. Returns Err if the Service does not exist or /// was incorrectly cast. pub(crate) fn try_get(map: &Map, name: &str) -> Result> where T: Any + Send + Sync + Sized, { map.read() .expect("locked for reading") .get(name) .map_or_else( || Err!("Service {name:?} does not exist or has not been built yet."), |(_, s)| { s.upgrade().map_or_else( || Err!("Service {name:?} no longer exists."), |s| { s.downcast::() .map_err(|_| err!("Service {name:?} must be correctly downcast.")) }, ) }, ) } /// Utility for service implementations; see Service::name() in the trait. #[inline] pub(crate) fn make_name(module_path: &str) -> &str { module_path.split_once_infallible("::").1 }