use std::{ any::Any, collections::BTreeMap, fmt::Write, ops::Deref, sync::{Arc, OnceLock}, }; use async_trait::async_trait; use conduit::{err, error::inspect_log, utils::string::split_once_infallible, 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 to allow the /// referenced service construction after the referencing service. pub(crate) struct Dep { dep: OnceLock>, service: Arc, name: &'static str, } pub(crate) type Map = BTreeMap; pub(crate) type MapVal = (Arc, Arc); impl Deref for Dep { type Target = Arc; fn deref(&self) -> &Self::Target { self.dep .get_or_init(|| require::(&self.service, self.name)) } } impl Args<'_> { pub(crate) fn depend_service(&self, name: &'static str) -> Dep { Dep:: { dep: OnceLock::new(), service: self.service.clone(), name, } } } pub(crate) fn require(map: &Map, name: &str) -> Arc { try_get::(map, name) .inspect_err(inspect_log) .expect("Failure to reference service required by another service.") } pub(crate) fn try_get(map: &Map, name: &str) -> Result> { map.get(name).map_or_else( || Err!("Service {name:?} does not exist or has not been built yet."), |(_, s)| { s.clone() .downcast::() .map_err(|_| err!("Service {name:?} must be correctly downcast.")) }, ) } pub(crate) fn get(map: &Map, name: &str) -> Option> { map.get(name).map(|(_, s)| { s.clone() .downcast::() .expect("Service must be correctly downcast.") }) } #[inline] pub(crate) fn make_name(module_path: &str) -> &str { split_once_infallible(module_path, "::").1 }