diff --git a/backend/src/config/mod.rs b/backend/src/config/mod.rs index 53cffbcef..b5004df31 100644 --- a/backend/src/config/mod.rs +++ b/backend/src/config/mod.rs @@ -14,6 +14,7 @@ use serde_json::Value; use tracing::instrument; use crate::context::RpcContext; +use crate::db::model::{CurrentDependencies, CurrentDependents}; use crate::dependencies::{ add_dependent_to_current_dependents_lists, break_transitive, heal_all_dependents_transitive, BreakageRes, DependencyConfig, DependencyError, DependencyErrors, TaggedDependencyError, @@ -276,7 +277,8 @@ pub struct ConfigReceipts { version: LockReceipt, manifest: LockReceipt, system_pointers: LockReceipt, String>, - pub current_dependents: LockReceipt, String>, + pub current_dependents: LockReceipt, + pub current_dependencies: LockReceipt, dependency_errors: LockReceipt, manifest_dependencies_config: LockReceipt, } @@ -360,6 +362,14 @@ impl ConfigReceipts { .make_locker(LockType::Write) .add_to_keys(locks); + let current_dependencies = crate::db::DatabaseModel::new() + .package_data() + .star() + .installed() + .map(|x| x.current_dependencies()) + .make_locker(LockType::Write) + .add_to_keys(locks); + let dependency_errors = crate::db::DatabaseModel::new() .package_data() .star() @@ -391,6 +401,7 @@ impl ConfigReceipts { manifest: manifest.verify(skeleton_key)?, system_pointers: system_pointers.verify(skeleton_key)?, current_dependents: current_dependents.verify(skeleton_key)?, + current_dependencies: current_dependencies.verify(skeleton_key)?, dependency_errors: dependency_errors.verify(skeleton_key)?, manifest_dependencies_config: manifest_dependencies_config.verify(skeleton_key)?, }) @@ -544,26 +555,28 @@ pub fn configure_rec<'a, Db: DbHandle>( .await? .ok_or_else(not_found)?; sys.truncate(0); - let mut current_dependencies: BTreeMap = dependencies - .0 - .iter() - .filter_map(|(id, info)| { - if info.requirement.required() { - Some((id.clone(), CurrentDependencyInfo::default())) - } else { - None - } - }) - .collect(); + let mut current_dependencies: CurrentDependencies = CurrentDependencies( + dependencies + .0 + .iter() + .filter_map(|(id, info)| { + if info.requirement.required() { + Some((id.clone(), CurrentDependencyInfo::default())) + } else { + None + } + }) + .collect(), + ); for ptr in spec.pointers(&config)? { match ptr { ValueSpecPointer::Package(pkg_ptr) => { if let Some(current_dependency) = - current_dependencies.get_mut(pkg_ptr.package_id()) + current_dependencies.0.get_mut(pkg_ptr.package_id()) { current_dependency.pointers.push(pkg_ptr); } else { - current_dependencies.insert( + current_dependencies.0.insert( pkg_ptr.package_id().to_owned(), CurrentDependencyInfo { pointers: vec![pkg_ptr], @@ -585,10 +598,10 @@ pub fn configure_rec<'a, Db: DbHandle>( // track dependencies with no pointers for (package_id, health_checks) in res.depends_on.into_iter() { - if let Some(current_dependency) = current_dependencies.get_mut(&package_id) { + if let Some(current_dependency) = current_dependencies.0.get_mut(&package_id) { current_dependency.health_checks.extend(health_checks); } else { - current_dependencies.insert( + current_dependencies.0.insert( package_id, CurrentDependencyInfo { pointers: Vec::new(), @@ -599,17 +612,18 @@ pub fn configure_rec<'a, Db: DbHandle>( } // track dependency health checks - current_dependencies = current_dependencies - .into_iter() - .filter(|(dep_id, _)| { - if dep_id != id && !manifest.dependencies.0.contains_key(dep_id) { - tracing::warn!("Illegal dependency specified: {}", dep_id); - false - } else { - true - } - }) - .collect(); + current_dependencies = current_dependencies.map(|x| { + x.into_iter() + .filter(|(dep_id, _)| { + if dep_id != id && !manifest.dependencies.0.contains_key(dep_id) { + tracing::warn!("Illegal dependency specified: {}", dep_id); + false + } else { + true + } + }) + .collect() + }); res.signal } else { None @@ -619,7 +633,7 @@ pub fn configure_rec<'a, Db: DbHandle>( remove_from_current_dependents_lists( db, id, - current_dependencies.keys(), + ¤t_dependencies, &receipts.current_dependents, ) .await?; // remove previous @@ -630,9 +644,9 @@ pub fn configure_rec<'a, Db: DbHandle>( &receipts.current_dependents, ) .await?; // add new - current_dependencies.remove(id); + current_dependencies.0.remove(id); receipts - .current_dependents + .current_dependencies .set(db, current_dependencies.clone(), &id) .await?; @@ -656,12 +670,16 @@ pub fn configure_rec<'a, Db: DbHandle>( overrides.insert(id.clone(), config.clone()); // handle dependents - let dependents = current_dependencies; + let dependents = receipts + .current_dependents + .get(db, id) + .await? + .ok_or_else(not_found)?; let prev = if is_needs_config { None } else { old_config } .map(Value::Object) .unwrap_or_default(); let next = Value::Object(config.clone()); - for (dependent, dep_info) in dependents.iter().filter(|(dep_id, _)| dep_id != &id) { + for (dependent, dep_info) in dependents.0.iter().filter(|(dep_id, _)| dep_id != &id) { // check if config passes dependent check if let Some(cfg) = receipts .manifest_dependencies_config diff --git a/backend/src/db/model.rs b/backend/src/db/model.rs index 7d34091e7..549f42938 100644 --- a/backend/src/db/model.rs +++ b/backend/src/db/model.rs @@ -265,13 +265,61 @@ pub struct InstalledPackageDataEntry { #[model] pub dependency_info: BTreeMap, #[model] - pub current_dependents: BTreeMap, + pub current_dependents: CurrentDependents, #[model] - pub current_dependencies: BTreeMap, + pub current_dependencies: CurrentDependencies, #[model] pub interface_addresses: InterfaceAddressMap, } +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct CurrentDependents(pub BTreeMap); +impl CurrentDependents { + pub fn map( + mut self, + transform: impl Fn( + BTreeMap, + ) -> BTreeMap, + ) -> Self { + self.0 = transform(self.0); + self + } +} +impl Map for CurrentDependents { + type Key = PackageId; + type Value = CurrentDependencyInfo; + fn get(&self, key: &Self::Key) -> Option<&Self::Value> { + self.0.get(key) + } +} +impl HasModel for CurrentDependents { + type Model = MapModel; +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct CurrentDependencies(pub BTreeMap); +impl CurrentDependencies { + pub fn map( + mut self, + transform: impl Fn( + BTreeMap, + ) -> BTreeMap, + ) -> Self { + self.0 = transform(self.0); + self + } +} +impl Map for CurrentDependencies { + type Key = PackageId; + type Value = CurrentDependencyInfo; + fn get(&self, key: &Self::Key) -> Option<&Self::Value> { + self.0.get(key) + } +} +impl HasModel for CurrentDependencies { + type Model = MapModel; +} + #[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] pub struct StaticDependencyInfo { diff --git a/backend/src/dependencies.rs b/backend/src/dependencies.rs index f5dae92ad..677755dad 100644 --- a/backend/src/dependencies.rs +++ b/backend/src/dependencies.rs @@ -17,7 +17,9 @@ use tracing::instrument; use crate::config::spec::PackagePointerSpec; use crate::config::{not_found, Config, ConfigReceipts, ConfigSpec}; use crate::context::RpcContext; -use crate::db::model::{CurrentDependencyInfo, InstalledPackageDataEntry}; +use crate::db::model::{ + CurrentDependencies, CurrentDependencyInfo, CurrentDependents, InstalledPackageDataEntry, +}; use crate::procedure::{NoOutput, PackageProcedure}; use crate::s9pk::manifest::{Manifest, PackageId}; use crate::status::health_check::{HealthCheckId, HealthCheckResult}; @@ -64,7 +66,7 @@ pub struct TryHealReceipts { status: LockReceipt, manifest: LockReceipt, manifest_version: LockReceipt, - current_dependencies: LockReceipt, String>, + current_dependencies: LockReceipt, dependency_errors: LockReceipt, } @@ -541,6 +543,7 @@ impl DependencyConfig { } pub struct DependencyConfigReceipts { + config: ConfigReceipts, dependencies: LockReceipt, dependency_volumes: LockReceipt, dependency_version: LockReceipt, @@ -550,65 +553,76 @@ pub struct DependencyConfigReceipts { } impl DependencyConfigReceipts { - pub async fn new( - db: &'_ mut impl DbHandle, + pub async fn new<'a>( + db: &'a mut impl DbHandle, package_id: &PackageId, dependency_id: &PackageId, ) -> Result { let mut locks = Vec::new(); + let setup = Self::setup(&mut locks, package_id, dependency_id); + Ok(setup(&db.lock_all(locks).await?)?) + } + + pub fn setup( + locks: &mut Vec, + package_id: &PackageId, + dependency_id: &PackageId, + ) -> impl FnOnce(&Verifier) -> Result { + let config = ConfigReceipts::setup(locks); let dependencies = crate::db::DatabaseModel::new() .package_data() .idx_model(package_id) .and_then(|x| x.installed()) .map(|x| x.manifest().dependencies()) .make_locker(LockType::Write) - .add_to_keys(&mut locks); + .add_to_keys(locks); let dependency_volumes = crate::db::DatabaseModel::new() .package_data() .idx_model(dependency_id) .and_then(|x| x.installed()) .map(|x| x.manifest().volumes()) .make_locker(LockType::Write) - .add_to_keys(&mut locks); + .add_to_keys(locks); let dependency_version = crate::db::DatabaseModel::new() .package_data() .idx_model(dependency_id) .and_then(|x| x.installed()) .map(|x| x.manifest().version()) .make_locker(LockType::Write) - .add_to_keys(&mut locks); + .add_to_keys(locks); let dependency_config_action = crate::db::DatabaseModel::new() .package_data() .idx_model(dependency_id) .and_then(|x| x.installed()) .and_then(|x| x.manifest().config()) .make_locker(LockType::Write) - .add_to_keys(&mut locks); + .add_to_keys(locks); let package_volumes = crate::db::DatabaseModel::new() .package_data() .idx_model(package_id) .and_then(|x| x.installed()) .map(|x| x.manifest().volumes()) .make_locker(LockType::Write) - .add_to_keys(&mut locks); + .add_to_keys(locks); let package_version = crate::db::DatabaseModel::new() .package_data() .idx_model(package_id) .and_then(|x| x.installed()) .map(|x| x.manifest().version()) .make_locker(LockType::Write) - .add_to_keys(&mut locks); - - let skeleton_key = db.lock_all(locks).await?; - Ok(Self { - dependencies: dependencies.verify(&skeleton_key)?, - dependency_volumes: dependency_volumes.verify(&skeleton_key)?, - dependency_version: dependency_version.verify(&skeleton_key)?, - dependency_config_action: dependency_config_action.verify(&skeleton_key)?, - package_volumes: package_volumes.verify(&skeleton_key)?, - package_version: package_version.verify(&skeleton_key)?, - }) + .add_to_keys(locks); + move |skeleton_key| { + Ok(Self { + config: config(skeleton_key)?, + dependencies: dependencies.verify(&skeleton_key)?, + dependency_volumes: dependency_volumes.verify(&skeleton_key)?, + dependency_version: dependency_version.verify(&skeleton_key)?, + dependency_config_action: dependency_config_action.verify(&skeleton_key)?, + package_volumes: package_volumes.verify(&skeleton_key)?, + package_version: package_version.verify(&skeleton_key)?, + }) + } } } @@ -635,7 +649,7 @@ pub async fn configure_impl( spec: _, } = configure_logic(ctx.clone(), &mut db, (pkg_id, dep_id.clone()), &receipts).await?; - let locks = ConfigReceipts::new(&mut db).await?; + let locks = &receipts.config; Ok(crate::config::configure( &ctx, &mut db, @@ -645,7 +659,7 @@ pub async fn configure_impl( false, &mut BTreeMap::new(), &mut BTreeMap::new(), - &locks, + locks, ) .await?) } @@ -749,21 +763,19 @@ pub async fn configure_logic( }) } #[instrument(skip(db, current_dependencies, current_dependent_receipt))] -pub async fn add_dependent_to_current_dependents_lists< - 'a, - Db: DbHandle, - I: IntoIterator, ->( +pub async fn add_dependent_to_current_dependents_lists<'a, Db: DbHandle>( db: &mut Db, dependent_id: &PackageId, - current_dependencies: I, - current_dependent_receipt: &LockReceipt, String>, + current_dependencies: &CurrentDependencies, + current_dependent_receipt: &LockReceipt, ) -> Result<(), Error> { - for (dependency, dep_info) in current_dependencies { + for (dependency, dep_info) in ¤t_dependencies.0 { if let Some(mut dependency_dependents) = current_dependent_receipt.get(db, dependency).await? { - dependency_dependents.insert(dependent_id.clone(), dep_info.clone()); + dependency_dependents + .0 + .insert(dependent_id.clone(), dep_info.clone()); current_dependent_receipt .set(db, dependency_dependents, dependency) .await?; @@ -789,11 +801,11 @@ impl DependencyErrors { ctx: &RpcContext, db: &mut Db, manifest: &Manifest, - current_dependencies: &BTreeMap, + current_dependencies: &CurrentDependencies, receipts: &TryHealReceipts, ) -> Result { let mut res = BTreeMap::new(); - for (dependency_id, info) in current_dependencies.keys().filter_map(|dependency_id| { + for (dependency_id, info) in current_dependencies.0.keys().filter_map(|dependency_id| { manifest .dependencies .0 @@ -836,7 +848,7 @@ pub async fn break_all_dependents_transitive<'a, Db: DbHandle>( .get(db, id) .await? .iter() - .flat_map(|x| x.keys()) + .flat_map(|x| x.0.keys()) .filter(|dependent| id != *dependent) { break_transitive(db, dependent, id, error.clone(), breakages, receipts).await?; @@ -848,7 +860,7 @@ pub async fn break_all_dependents_transitive<'a, Db: DbHandle>( pub struct BreakTransitiveReceipts { pub dependency_receipt: DependencyReceipt, dependency_errors: LockReceipt, - current_dependents: LockReceipt, String>, + current_dependents: LockReceipt, } impl BreakTransitiveReceipts { @@ -964,7 +976,7 @@ pub async fn heal_all_dependents_transitive<'a, Db: DbHandle>( .get(db, id) .await? .ok_or_else(not_found)?; - for dependent in dependents.keys().filter(|dependent| id != *dependent) { + for dependent in dependents.0.keys().filter(|dependent| id != *dependent) { heal_transitive(ctx, db, dependent, id, locks).await?; } Ok(()) @@ -1014,7 +1026,7 @@ pub async fn reconfigure_dependents_with_live_pointers( ) -> Result<(), Error> { let dependents = &pde.current_dependents; let me = &pde.manifest.id; - for (dependent_id, dependency_info) in dependents { + for (dependent_id, dependency_info) in &dependents.0 { if dependency_info.pointers.iter().any(|ptr| match ptr { // dependency id matches the package being uninstalled PackagePointerSpec::TorAddress(ptr) => &ptr.package_id == me && dependent_id != me, @@ -1043,7 +1055,7 @@ pub async fn reconfigure_dependents_with_live_pointers( #[derive(Clone)] pub struct DependencyReceipt { pub try_heal: TryHealReceipts, - current_dependents: LockReceipt, String>, + current_dependents: LockReceipt, status: LockReceipt, dependency: LockReceipt, } diff --git a/backend/src/install/cleanup.rs b/backend/src/install/cleanup.rs index aa2b52ce4..efa6f43e2 100644 --- a/backend/src/install/cleanup.rs +++ b/backend/src/install/cleanup.rs @@ -9,7 +9,8 @@ use super::{PKG_ARCHIVE_DIR, PKG_DOCKER_DIR}; use crate::config::{not_found, ConfigReceipts}; use crate::context::RpcContext; use crate::db::model::{ - AllPackageData, CurrentDependencyInfo, InstalledPackageDataEntry, PackageDataEntry, + AllPackageData, CurrentDependencies, CurrentDependencyInfo, CurrentDependents, + InstalledPackageDataEntry, PackageDataEntry, }; use crate::dependencies::{ reconfigure_dependents_with_live_pointers, DependencyErrors, TryHealReceipts, @@ -60,18 +61,14 @@ impl UpdateDependencyReceipts { } #[instrument(skip(ctx, db, deps, receipts))] -pub async fn update_dependency_errors_of_dependents< - 'a, - Db: DbHandle, - I: IntoIterator, ->( +pub async fn update_dependency_errors_of_dependents<'a, Db: DbHandle>( ctx: &RpcContext, db: &mut Db, id: &PackageId, - deps: I, + deps: &CurrentDependents, receipts: &UpdateDependencyReceipts, ) -> Result<(), Error> { - for dep in deps { + for dep in deps.0.keys() { if let Some(man) = receipts.manifest.get(db, dep).await? { if let Err(e) = if let Some(info) = man.dependencies.0.get(id) { info.satisfied(ctx, db, id, None, dep, &receipts.try_heal) @@ -269,19 +266,15 @@ pub async fn cleanup_failed( } #[instrument(skip(db, current_dependencies, current_dependent_receipt))] -pub async fn remove_from_current_dependents_lists< - 'a, - Db: DbHandle, - I: IntoIterator, ->( +pub async fn remove_from_current_dependents_lists<'a, Db: DbHandle>( db: &mut Db, id: &'a PackageId, - current_dependencies: I, - current_dependent_receipt: &LockReceipt, String>, + current_dependencies: &'a CurrentDependencies, + current_dependent_receipt: &LockReceipt, ) -> Result<(), Error> { - for dep in current_dependencies.into_iter().chain(std::iter::once(id)) { + for dep in current_dependencies.0.keys().chain(std::iter::once(id)) { if let Some(mut current_dependents) = current_dependent_receipt.get(db, dep).await? { - if current_dependents.remove(id).is_some() { + if current_dependents.0.remove(id).is_some() { current_dependent_receipt .set(db, current_dependents, dep) .await?; @@ -294,7 +287,7 @@ pub struct UninstallReceipts { config: ConfigReceipts, removing: LockReceipt, packages: LockReceipt, - current_dependents: LockReceipt, String>, + current_dependents: LockReceipt, update_depenency_receipts: UpdateDependencyReceipts, } impl UninstallReceipts { @@ -367,7 +360,7 @@ where remove_from_current_dependents_lists( &mut tx, &entry.manifest.id, - entry.current_dependencies.keys(), + &entry.current_dependencies, &receipts.current_dependents, ) .await?; @@ -375,7 +368,7 @@ where ctx, &mut tx, &entry.manifest.id, - entry.current_dependents.keys(), + &entry.current_dependents, &receipts.update_depenency_receipts, ) .await?; diff --git a/backend/src/install/mod.rs b/backend/src/install/mod.rs index 3ebd3019a..67ce1cf1c 100644 --- a/backend/src/install/mod.rs +++ b/backend/src/install/mod.rs @@ -29,8 +29,8 @@ use crate::config::ConfigReceipts; use crate::context::{CliContext, RpcContext}; use crate::core::rpc_continuations::{RequestGuid, RpcContinuation}; use crate::db::model::{ - CurrentDependencyInfo, InstalledPackageDataEntry, PackageDataEntry, RecoveredPackageInfo, - StaticDependencyInfo, StaticFiles, + CurrentDependencies, CurrentDependencyInfo, CurrentDependents, InstalledPackageDataEntry, + PackageDataEntry, RecoveredPackageInfo, StaticDependencyInfo, StaticFiles, }; use crate::db::util::WithRevision; use crate::dependencies::{ @@ -1168,18 +1168,20 @@ pub async fn install_s9pk( tracing::info!("Install {}@{}: Created manager", pkg_id, version); let static_files = StaticFiles::local(pkg_id, version, manifest.assets.icon_type()); - let current_dependencies: BTreeMap<_, _> = manifest - .dependencies - .0 - .iter() - .filter_map(|(id, info)| { - if info.requirement.required() { - Some((id.clone(), CurrentDependencyInfo::default())) - } else { - None - } - }) - .collect(); + let current_dependencies: CurrentDependencies = CurrentDependencies( + manifest + .dependencies + .0 + .iter() + .filter_map(|(id, info)| { + if info.requirement.required() { + Some((id.clone(), CurrentDependencyInfo::default())) + } else { + None + } + }) + .collect(), + ); let current_dependents = { let mut deps = BTreeMap::new(); for package in crate::db::DatabaseModel::new() @@ -1225,7 +1227,7 @@ pub async fn install_s9pk( deps.insert(package, dep); } } - deps + CurrentDependents(deps) }; let mut pde = model .clone() @@ -1360,7 +1362,7 @@ pub async fn install_s9pk( remove_from_current_dependents_lists( &mut tx, pkg_id, - prev.current_dependencies.keys(), + &prev.current_dependencies, &receipts.config.current_dependents, ) .await?; // remove previous @@ -1375,10 +1377,11 @@ pub async fn install_s9pk( ctx, &mut tx, pkg_id, - current_dependents - .keys() - .chain(prev.current_dependents.keys()) - .collect::>(), + &CurrentDependents({ + let mut current_dependents = current_dependents.0.clone(); + current_dependents.append(&mut prev.current_dependents.0.clone()); + current_dependents + }), &receipts.config.update_dependency_receipts, ) .await?; @@ -1409,7 +1412,7 @@ pub async fn install_s9pk( ctx, &mut tx, pkg_id, - current_dependents.keys(), + ¤t_dependents, &receipts.config.update_dependency_receipts, ) .await?; @@ -1441,7 +1444,7 @@ pub async fn install_s9pk( ctx, &mut tx, pkg_id, - current_dependents.keys(), + ¤t_dependents, &receipts.config.update_dependency_receipts, ) .await?; @@ -1457,7 +1460,7 @@ pub async fn install_s9pk( ctx, &mut tx, pkg_id, - current_dependents.keys(), + ¤t_dependents, &receipts.config.update_dependency_receipts, ) .await?; diff --git a/backend/src/manager/health.rs b/backend/src/manager/health.rs index 5e9b8a62f..d48e44f7e 100644 --- a/backend/src/manager/health.rs +++ b/backend/src/manager/health.rs @@ -102,7 +102,7 @@ pub async fn check( let receipts = crate::dependencies::BreakTransitiveReceipts::new(&mut tx).await?; tracing::debug!("Got receipts {}", id); - for (dependent, info) in &*current_dependents { + for (dependent, info) in (*current_dependents).0.iter() { let failures: BTreeMap = health_results .iter() .filter(|(_, hc_res)| !matches!(hc_res, HealthCheckResult::Success { .. })) diff --git a/backend/src/procedure/mod.rs b/backend/src/procedure/mod.rs index 966b32fe0..63805786a 100644 --- a/backend/src/procedure/mod.rs +++ b/backend/src/procedure/mod.rs @@ -159,6 +159,7 @@ impl PackageProcedure { } } +#[derive(Debug)] pub struct NoOutput; impl<'de> Deserialize<'de> for NoOutput { fn deserialize(_: D) -> Result