add indirection for circular-dependencies between services
Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
parent
3dc91525ce
commit
9b20c6918f
3 changed files with 66 additions and 23 deletions
|
@ -29,7 +29,7 @@ use std::sync::{Arc, RwLock};
|
||||||
pub(crate) use conduit::{config, debug_error, debug_warn, utils, Error, Result, Server};
|
pub(crate) use conduit::{config, debug_error, debug_warn, utils, Error, Result, Server};
|
||||||
pub use conduit::{pdu, PduBuilder, PduCount, PduEvent};
|
pub use conduit::{pdu, PduBuilder, PduCount, PduEvent};
|
||||||
use database::Database;
|
use database::Database;
|
||||||
pub(crate) use service::{Args, Service};
|
pub(crate) use service::{Args, Dep, Service};
|
||||||
|
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
globals::{server_is_ours, user_is_local},
|
globals::{server_is_ours, user_is_local},
|
||||||
|
|
|
@ -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 async_trait::async_trait;
|
||||||
use conduit::{err, error::inspect_log, utils::string::split_once_infallible, Err, Result, Server};
|
use conduit::{err, error::inspect_log, utils::string::split_once_infallible, Err, Result, Server};
|
||||||
use database::Database;
|
use database::Database;
|
||||||
|
|
||||||
|
/// Abstract interface for a Service
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub(crate) trait Service: Any + Send + Sync {
|
pub(crate) trait Service: Any + Send + Sync {
|
||||||
/// Implement the construction of the service instance. Services are
|
/// Implement the construction of the service instance. Services are
|
||||||
|
@ -34,27 +41,51 @@ pub(crate) trait Service: Any + Send + Sync {
|
||||||
fn name(&self) -> &str;
|
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) struct Args<'a> {
|
||||||
pub(crate) server: &'a Arc<Server>,
|
pub(crate) server: &'a Arc<Server>,
|
||||||
pub(crate) db: &'a Arc<Database>,
|
pub(crate) db: &'a Arc<Database>,
|
||||||
pub(crate) service: &'a Map,
|
pub(crate) service: &'a Arc<Map>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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<T> {
|
||||||
|
dep: OnceLock<Arc<T>>,
|
||||||
|
service: Arc<Map>,
|
||||||
|
name: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) type Map = BTreeMap<String, MapVal>;
|
pub(crate) type Map = BTreeMap<String, MapVal>;
|
||||||
pub(crate) type MapVal = (Arc<dyn Service>, Arc<dyn Any + Send + Sync>);
|
pub(crate) type MapVal = (Arc<dyn Service>, Arc<dyn Any + Send + Sync>);
|
||||||
|
|
||||||
impl Args<'_> {
|
impl<T: Any + Send + Sync> Deref for Dep<T> {
|
||||||
pub(crate) fn require_service<T: Any + Send + Sync>(&self, name: &str) -> Arc<T> {
|
type Target = Arc<T>;
|
||||||
self.try_get_service::<T>(name)
|
|
||||||
.inspect_err(inspect_log)
|
|
||||||
.expect("Failure to reference service required by another service.")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn try_get_service<T: Any + Send + Sync>(&self, name: &str) -> Result<Arc<T>> {
|
fn deref(&self) -> &Self::Target {
|
||||||
try_get::<T>(self.service, name)
|
self.dep
|
||||||
|
.get_or_init(|| require::<T>(&self.service, self.name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Args<'_> {
|
||||||
|
pub(crate) fn depend_service<T: Any + Send + Sync>(&self, name: &'static str) -> Dep<T> {
|
||||||
|
Dep::<T> {
|
||||||
|
dep: OnceLock::new(),
|
||||||
|
service: self.service.clone(),
|
||||||
|
name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn require<T: Any + Send + Sync>(map: &Map, name: &str) -> Arc<T> {
|
||||||
|
try_get::<T>(map, name)
|
||||||
|
.inspect_err(inspect_log)
|
||||||
|
.expect("Failure to reference service required by another service.")
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn try_get<T: Any + Send + Sync>(map: &Map, name: &str) -> Result<Arc<T>> {
|
pub(crate) fn try_get<T: Any + Send + Sync>(map: &Map, name: &str) -> Result<Arc<T>> {
|
||||||
map.get(name).map_or_else(
|
map.get(name).map_or_else(
|
||||||
|| Err!("Service {name:?} does not exist or has not been built yet."),
|
|| Err!("Service {name:?} does not exist or has not been built yet."),
|
||||||
|
|
|
@ -31,24 +31,36 @@ pub struct Services {
|
||||||
pub updates: Arc<updates::Service>,
|
pub updates: Arc<updates::Service>,
|
||||||
|
|
||||||
manager: Mutex<Option<Arc<Manager>>>,
|
manager: Mutex<Option<Arc<Manager>>>,
|
||||||
pub(crate) service: Map,
|
pub(crate) service: Arc<Map>,
|
||||||
pub server: Arc<Server>,
|
pub server: Arc<Server>,
|
||||||
pub db: Arc<Database>,
|
pub db: Arc<Database>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
impl Services {
|
||||||
|
#[allow(clippy::cognitive_complexity)]
|
||||||
pub fn build(server: Arc<Server>, db: Arc<Database>) -> Result<Self> {
|
pub fn build(server: Arc<Server>, db: Arc<Database>) -> Result<Self> {
|
||||||
let mut service: Map = BTreeMap::new();
|
let mut service: Arc<Map> = Arc::new(BTreeMap::new());
|
||||||
macro_rules! build {
|
macro_rules! build {
|
||||||
($tyname:ty) => {{
|
($srv:ty) => {
|
||||||
let built = <$tyname>::build(Args {
|
build_service!(service, server, db, $srv)
|
||||||
server: &server,
|
};
|
||||||
db: &db,
|
|
||||||
service: &service,
|
|
||||||
})?;
|
|
||||||
service.insert(built.name().to_owned(), (built.clone(), built.clone()));
|
|
||||||
built
|
|
||||||
}};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
@ -167,7 +179,7 @@ impl Services {
|
||||||
fn interrupt(&self) {
|
fn interrupt(&self) {
|
||||||
debug!("Interrupting services...");
|
debug!("Interrupting services...");
|
||||||
|
|
||||||
for (name, (service, ..)) in &self.service {
|
for (name, (service, ..)) in self.service.iter() {
|
||||||
trace!("Interrupting {name}");
|
trace!("Interrupting {name}");
|
||||||
service.interrupt();
|
service.interrupt();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue