A library for writing host-specific, single-binary configuration management and deployment tools
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

76 lines
2.4 KiB

use super::{Add, AddResult, AddableResource};
use crate::resources::{FromArtifact, FromResource};
use async_trait::async_trait;
use futures_util::future::{FutureExt, Shared};
use slog::{trace, Logger};
use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt::Debug;
use std::future::Future;
use std::hash::Hash;
use std::pin::Pin;
use std::rc::Rc;
// FIXME: Switch Error to Rc
type ResourceCache<Rs, As> =
HashMap<Rs, Shared<Pin<Box<dyn Future<Output = Result<(As, bool), String>>>>>>;
#[derive(Debug)]
pub struct Cache<I, Rs, As> {
resources: RefCell<ResourceCache<Rs, As>>,
inner: Rc<I>,
}
impl<I, Rs, As> Cache<I, Rs, As> {
pub fn new(inner: I) -> Self {
Self {
resources: RefCell::default(),
inner: Rc::new(inner),
}
}
}
#[async_trait(?Send)]
impl<R: AddableResource + Debug, I, Rs, As> Add<R> for Cache<I, Rs, As>
where
Rs: Hash + Eq + 'static + FromResource<R>,
As: 'static + FromArtifact<R> + Clone,
I: 'static + Add<R>,
{
// FIXME: https://github.com/rust-lang/rust-clippy/issues/6353
#[allow(clippy::await_holding_refcell_ref)]
async fn add(&self, logger: &Rc<Logger>, resource: Rc<R>, force_run: bool) -> AddResult<R> {
let (storable_resource, weak_resource) = Rs::from_resource(&resource);
let mut resources = self.resources.borrow_mut();
let future = if let Some(future) = resources.get(&storable_resource) {
assert!(
!force_run,
"Forcing to run an already-added resource is a logical error"
);
trace!(logger, "Resource already added");
future.clone()
} else {
let inner_weak = Rc::downgrade(&self.inner);
let logger_weak = Rc::downgrade(logger);
let future = (Box::pin(async move {
let inner = inner_weak.upgrade().expect("Dangling!");
let logger = logger_weak.upgrade().expect("Dangling!");
let resource = weak_resource.upgrade().expect("Dangling!");
let result = inner.add(&logger, Rc::clone(&resource), force_run).await;
// Need to convert Box<Error> to String for Clone for Shared
result
.map(|(t, did_run)| (As::from_artifact(t), did_run))
.map_err(|e| e.to_string())
}) as Pin<Box<dyn Future<Output = Result<(As, bool), String>>>>)
.shared();
resources.insert(storable_resource, future.clone());
future
};
drop(resources);
future
.await
.map(|(t, did_run)| (t.into_artifact(), did_run))
.map_err(std::convert::Into::into)
}
}