diff --git a/src/service/mod.rs b/src/service/mod.rs index 46adb072..21d1f594 100644 --- a/src/service/mod.rs +++ b/src/service/mod.rs @@ -29,7 +29,7 @@ use std::sync::{Arc, RwLock}; pub(crate) use conduit::{config, debug_error, debug_warn, utils, Error, Result, Server}; pub use conduit::{pdu, PduBuilder, PduCount, PduEvent}; use database::Database; -pub(crate) use service::{Args, Service}; +pub(crate) use service::{Args, Dep, Service}; pub use crate::{ globals::{server_is_ours, user_is_local}, diff --git a/src/service/service.rs b/src/service/service.rs index 863b955b..ce4f15b2 100644 --- a/src/service/service.rs +++ b/src/service/service.rs @@ -1,9 +1,16 @@ -use std::{any::Any, collections::BTreeMap, fmt::Write, sync::Arc}; +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 @@ -34,27 +41,51 @@ pub(crate) trait Service: Any + Send + Sync { 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 Map, + 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 Args<'_> { - pub(crate) fn require_service(&self, name: &str) -> Arc { - self.try_get_service::(name) - .inspect_err(inspect_log) - .expect("Failure to reference service required by another service.") - } +impl Deref for Dep { + type Target = Arc; - pub(crate) fn try_get_service(&self, name: &str) -> Result> { - try_get::(self.service, name) + 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."), diff --git a/src/service/services.rs b/src/service/services.rs index 136059cd..68205323 100644 --- a/src/service/services.rs +++ b/src/service/services.rs @@ -31,24 +31,36 @@ pub struct Services { pub updates: Arc, manager: Mutex>>, - pub(crate) service: Map, + pub(crate) service: Arc, pub server: Arc, pub db: Arc, } +macro_rules! build_service { + ($map:ident, $server:ident, $db:ident, $tyname:ty) => {{ + let built = <$tyname>::build(Args { + server: &$server, + db: &$db, + service: &$map, + })?; + + Arc::get_mut(&mut $map) + .expect("must have mutable reference to services collection") + .insert(built.name().to_owned(), (built.clone(), built.clone())); + + trace!("built service #{}: {:?}", $map.len(), built.name()); + built + }}; +} + impl Services { + #[allow(clippy::cognitive_complexity)] pub fn build(server: Arc, db: Arc) -> Result { - let mut service: Map = BTreeMap::new(); + let mut service: Arc = Arc::new(BTreeMap::new()); macro_rules! build { - ($tyname:ty) => {{ - let built = <$tyname>::build(Args { - server: &server, - db: &db, - service: &service, - })?; - service.insert(built.name().to_owned(), (built.clone(), built.clone())); - built - }}; + ($srv:ty) => { + build_service!(service, server, db, $srv) + }; } Ok(Self { @@ -167,7 +179,7 @@ impl Services { fn interrupt(&self) { debug!("Interrupting services..."); - for (name, (service, ..)) in &self.service { + for (name, (service, ..)) in self.service.iter() { trace!("Interrupting {name}"); service.interrupt(); }