use new locking api

This commit is contained in:
Aiden McClelland
2021-09-23 18:02:18 -06:00
committed by Aiden McClelland
parent 51417383d2
commit 660205c3db
13 changed files with 223 additions and 258 deletions

47
appmgr/Cargo.lock generated
View File

@@ -511,20 +511,6 @@ version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403" checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403"
[[package]]
name = "crossbeam"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae5588f6b3c3cb05239e90bd110f257254aecd01e4635400391aeae07497845"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-epoch",
"crossbeam-queue",
"crossbeam-utils",
]
[[package]] [[package]]
name = "crossbeam-channel" name = "crossbeam-channel"
version = "0.5.1" version = "0.5.1"
@@ -535,30 +521,6 @@ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]]
name = "crossbeam-deque"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-utils",
"lazy_static",
"memoffset",
"scopeguard",
]
[[package]] [[package]]
name = "crossbeam-queue" name = "crossbeam-queue"
version = "0.3.2" version = "0.3.2"
@@ -1918,7 +1880,6 @@ dependencies = [
"log", "log",
"nix 0.20.1", "nix 0.20.1",
"patch-db-macro", "patch-db-macro",
"qutex-2",
"serde", "serde",
"serde_cbor 0.11.1", "serde_cbor 0.11.1",
"serde_json", "serde_json",
@@ -2192,14 +2153,6 @@ dependencies = [
"proc-macro2 1.0.29", "proc-macro2 1.0.29",
] ]
[[package]]
name = "qutex-2"
version = "0.3.0"
dependencies = [
"crossbeam",
"futures",
]
[[package]] [[package]]
name = "radium" name = "radium"
version = "0.5.3" version = "0.5.3"

View File

@@ -344,6 +344,16 @@
"nullable": [] "nullable": []
} }
}, },
"a596bdc5014ba9e7b362398abf09ec6a100923e001247a79503d1e820ffe71c3": {
"query": "-- Add migration script here\nCREATE TABLE IF NOT EXISTS tor\n(\n package TEXT NOT NULL,\n interface TEXT NOT NULL,\n key BLOB NOT NULL CHECK (length(key) = 64),\n PRIMARY KEY (package, interface)\n);\nCREATE TABLE IF NOT EXISTS session\n(\n id TEXT NOT NULL PRIMARY KEY,\n logged_in TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n logged_out TIMESTAMP,\n last_active TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n user_agent TEXT,\n metadata TEXT NOT NULL DEFAULT 'null'\n);\nCREATE TABLE IF NOT EXISTS account\n(\n id INTEGER PRIMARY KEY CHECK (id = 0),\n password TEXT NOT NULL,\n tor_key BLOB NOT NULL CHECK (length(tor_key) = 64)\n);\nCREATE TABLE IF NOT EXISTS ssh_keys\n(\n fingerprint TEXT NOT NULL,\n openssh_pubkey TEXT NOT NULL,\n created_at TEXT NOT NULL,\n PRIMARY KEY (fingerprint)\n);\nCREATE TABLE IF NOT EXISTS certificates\n(\n id INTEGER PRIMARY KEY, -- Root = 0, Int = 1, Other = 2..\n priv_key_pem TEXT NOT NULL,\n certificate_pem TEXT NOT NULL,\n lookup_string TEXT UNIQUE,\n created_at TEXT,\n updated_at TEXT\n);\nCREATE TABLE IF NOT EXISTS notifications\n(\n id INTEGER PRIMARY KEY,\n package_id TEXT,\n created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n code INTEGER NOT NULL,\n level TEXT NOT NULL,\n title TEXT NOT NULL,\n message TEXT NOT NULL,\n data TEXT\n);",
"describe": {
"columns": [],
"parameters": {
"Right": 0
},
"nullable": []
}
},
"a6b0c8909a3a5d6d9156aebfb359424e6b5a1d1402e028219e21726f1ebd282e": { "a6b0c8909a3a5d6d9156aebfb359424e6b5a1d1402e028219e21726f1ebd282e": {
"query": "SELECT fingerprint, openssh_pubkey, created_at FROM ssh_keys", "query": "SELECT fingerprint, openssh_pubkey, created_at FROM ssh_keys",
"describe": { "describe": {

View File

@@ -14,12 +14,15 @@ use serde_json::Value;
use crate::action::docker::DockerAction; use crate::action::docker::DockerAction;
use crate::config::spec::PackagePointerSpecVariant; use crate::config::spec::PackagePointerSpecVariant;
use crate::context::RpcContext; use crate::context::RpcContext;
use crate::db::model::{CurrentDependencyInfo, InstalledPackageDataEntryModel}; use crate::db::model::{
CurrentDependencyInfo, InstalledPackageDataEntry, InstalledPackageDataEntryModel,
};
use crate::db::util::WithRevision; use crate::db::util::WithRevision;
use crate::dependencies::{ use crate::dependencies::{
update_current_dependents, BreakageRes, DependencyError, TaggedDependencyError, update_current_dependents, BreakageRes, DependencyError, TaggedDependencyError,
}; };
use crate::s9pk::manifest::PackageId; use crate::s9pk::manifest::PackageId;
use crate::status::handle_broken_dependents;
use crate::util::{ use crate::util::{
display_none, display_serializable, parse_duration, parse_stdin_deserializable, IoFormat, display_none, display_serializable, parse_duration, parse_stdin_deserializable, IoFormat,
}; };
@@ -281,7 +284,7 @@ pub fn configure<'a, Db: DbHandle>(
async move { async move {
crate::db::DatabaseModel::new() crate::db::DatabaseModel::new()
.package_data() .package_data()
.lock(db, patch_db::LockType::Write) .lock(db, true)
.await; .await;
// fetch data from db // fetch data from db
let pkg_model = crate::db::DatabaseModel::new() let pkg_model = crate::db::DatabaseModel::new()
@@ -401,77 +404,6 @@ pub fn configure<'a, Db: DbHandle>(
let prev = old_config.map(Value::Object).unwrap_or_default(); let prev = old_config.map(Value::Object).unwrap_or_default();
let next = Value::Object(config.clone()); 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.iter().filter(|(dep_id, _)| dep_id != &id) {
fn handle_broken_dependents<'a, Db: DbHandle>(
db: &'a mut Db,
id: &'a PackageId,
dependency: &'a PackageId,
model: InstalledPackageDataEntryModel,
error: DependencyError,
breakages: &'a mut IndexMap<PackageId, TaggedDependencyError>,
) -> BoxFuture<'a, Result<(), Error>> {
async move {
let mut status = model.clone().status().get_mut(db).await?;
let old = status.dependency_errors.0.remove(id);
let newly_broken = old.is_none();
status.dependency_errors.0.insert(
id.clone(),
if let Some(old) = old {
old.merge_with(error.clone())
} else {
error.clone()
},
);
if newly_broken {
breakages.insert(
id.clone(),
TaggedDependencyError {
dependency: dependency.clone(),
error: error.clone(),
},
);
if status.main.running() {
if model
.clone()
.manifest()
.dependencies()
.idx_model(dependency)
.expect(db)
.await?
.get(db, true)
.await?
.critical
{
status.main.stop();
let dependents = model.current_dependents().get(db, true).await?;
for (dependent, _) in &*dependents {
let dependent_model = crate::db::DatabaseModel::new()
.package_data()
.idx_model(dependent)
.and_then(|pkg| pkg.installed())
.expect(db)
.await?;
handle_broken_dependents(
db,
dependent,
id,
dependent_model,
DependencyError::NotRunning,
breakages,
)
.await?;
}
}
}
}
status.save(db).await?;
Ok(())
}
.boxed()
}
// check if config passes dependent check // check if config passes dependent check
let dependent_model = crate::db::DatabaseModel::new() let dependent_model = crate::db::DatabaseModel::new()
.package_data() .package_data()

View File

@@ -1592,7 +1592,7 @@ impl PackagePointerSpec {
if let Some(cfg) = config_overrides.get(&self.package_id) { if let Some(cfg) = config_overrides.get(&self.package_id) {
Ok(selector.select(*multi, &Value::Object(cfg.clone()))) Ok(selector.select(*multi, &Value::Object(cfg.clone())))
} else { } else {
let manifest_model: OptionModel<_> = crate::db::DatabaseModel::new() let manifest_model: OptionModel<Manifest> = crate::db::DatabaseModel::new()
.package_data() .package_data()
.idx_model(&self.package_id) .idx_model(&self.package_id)
.and_then(|pde| pde.installed()) .and_then(|pde| pde.installed())

View File

@@ -255,6 +255,8 @@ impl HasModel for InterfaceAddressMap {
#[derive(Debug, Deserialize, Serialize, HasModel)] #[derive(Debug, Deserialize, Serialize, HasModel)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct InterfaceAddresses { pub struct InterfaceAddresses {
#[model]
pub tor_address: Option<String>, pub tor_address: Option<String>,
#[model]
pub lan_address: Option<String>, pub lan_address: Option<String>,
} }

View File

@@ -122,7 +122,7 @@ pub async fn cleanup_failed<Db: DbHandle>(
.await? .await?
.get(db, true) .get(db, true)
.await? .await?
.to_owned(); .into_owned();
if match &pde { if match &pde {
PackageDataEntry::Installing { .. } => true, PackageDataEntry::Installing { .. } => true,
PackageDataEntry::Updating { manifest, .. } => { PackageDataEntry::Updating { manifest, .. } => {

View File

@@ -511,7 +511,7 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin>(
let mut sql_tx = ctx.secret_store.begin().await?; let mut sql_tx = ctx.secret_store.begin().await?;
crate::db::DatabaseModel::new() crate::db::DatabaseModel::new()
.package_data() .package_data()
.lock(&mut tx, patch_db::LockType::Write) .lock(&mut tx, true)
.await; .await;
log::info!("Install {}@{}: Creating volumes", pkg_id, version); log::info!("Install {}@{}: Creating volumes", pkg_id, version);

View File

@@ -38,14 +38,16 @@ impl ManagerMap {
.keys(db, true) .keys(db, true)
.await? .await?
{ {
let man = if let Some(installed) = crate::db::DatabaseModel::new() let man: Manifest = if let Some(manifest) = crate::db::DatabaseModel::new()
.package_data() .package_data()
.idx_model(&package) .idx_model(&package)
.and_then(|pkg| pkg.installed()) .and_then(|pkg| pkg.installed())
.check(db) .map(|m| m.manifest())
.get(db, true)
.await? .await?
.to_owned()
{ {
installed.manifest().get(db, true).await?.to_owned() manifest
} else { } else {
continue; continue;
}; };

View File

@@ -4,7 +4,7 @@ use std::str::FromStr;
use anyhow::anyhow; use anyhow::anyhow;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use patch_db::{LockType, PatchDb, Revision}; use patch_db::{PatchDb, Revision};
use rpc_toolkit::command; use rpc_toolkit::command;
use sqlx::SqlitePool; use sqlx::SqlitePool;
@@ -32,7 +32,7 @@ pub async fn list(
let model = crate::db::DatabaseModel::new() let model = crate::db::DatabaseModel::new()
.server_info() .server_info()
.unread_notification_count(); .unread_notification_count();
model.lock(&mut handle, patch_db::LockType::Write).await; model.lock(&mut handle, true).await;
let records = sqlx::query!( let records = sqlx::query!(
"SELECT id, package_id, created_at, code, level, title, message, data FROM notifications ORDER BY id DESC LIMIT ?", "SELECT id, package_id, created_at, code, level, title, message, data FROM notifications ORDER BY id DESC LIMIT ?",
limit limit

View File

@@ -4,8 +4,8 @@ use rpc_toolkit::command;
use serde_json::Value; use serde_json::Value;
use crate::context::RpcContext; use crate::context::RpcContext;
use crate::s9pk::manifest::PackageId; use crate::s9pk::manifest::{Manifest, PackageId};
use crate::{Error, ErrorKind, ResultExt}; use crate::{Error, ErrorKind};
pub fn display_properties(response: Value, _: &ArgMatches<'_>) { pub fn display_properties(response: Value, _: &ArgMatches<'_>) {
println!("{}", response); println!("{}", response);
@@ -18,17 +18,15 @@ pub async fn properties(#[context] ctx: RpcContext, #[arg] id: PackageId) -> Res
pub async fn fetch_properties(ctx: RpcContext, id: PackageId) -> Result<Value, Error> { pub async fn fetch_properties(ctx: RpcContext, id: PackageId) -> Result<Value, Error> {
let mut db = ctx.db.handle(); let mut db = ctx.db.handle();
let manifest = crate::db::DatabaseModel::new() let manifest: Manifest = crate::db::DatabaseModel::new()
.package_data() .package_data()
.idx_model(&id) .idx_model(&id)
.and_then(|p| p.installed()) .and_then(|p| p.installed())
.expect(&mut db) .map(|m| m.manifest())
.await
.with_kind(ErrorKind::NotFound)?
.manifest()
.get(&mut db, true) .get(&mut db, true)
.await? .await?
.to_owned(); .to_owned()
.ok_or_else(|| Error::new(anyhow!("{} is not installed", id), ErrorKind::NotFound))?;
if let Some(props) = manifest.properties { if let Some(props) = manifest.properties {
props props
.execute::<(), Value>( .execute::<(), Value>(
@@ -41,7 +39,7 @@ pub async fn fetch_properties(ctx: RpcContext, id: PackageId) -> Result<Value, E
false, false,
) )
.await? .await?
.map_err(|_| Error::new(anyhow!("Properties failure!"), crate::ErrorKind::Docker)) .map_err(|_| Error::new(anyhow!("Properties failure!"), ErrorKind::Docker))
.and_then(|a| Ok(a)) .and_then(|a| Ok(a))
} else { } else {
Ok(Value::Null) Ok(Value::Null)

View File

@@ -1,6 +1,6 @@
use std::sync::Arc; use std::sync::Arc;
use patch_db::{LockType, PatchDbHandle}; use patch_db::PatchDbHandle;
use rpc_toolkit::command; use rpc_toolkit::command;
use crate::context::RpcContext; use crate::context::RpcContext;
@@ -67,9 +67,7 @@ impl Shutdown {
#[command(display(display_none))] #[command(display(display_none))]
pub async fn shutdown(#[context] ctx: RpcContext) -> Result<(), Error> { pub async fn shutdown(#[context] ctx: RpcContext) -> Result<(), Error> {
let mut db = ctx.db.handle(); let mut db = ctx.db.handle();
crate::db::DatabaseModel::new() crate::db::DatabaseModel::new().lock(&mut db, true).await;
.lock(&mut db, LockType::Write)
.await;
ctx.shutdown ctx.shutdown
.send(Some(Shutdown { .send(Some(Shutdown {
zfs_pool: ctx.zfs_pool_name.clone(), zfs_pool: ctx.zfs_pool_name.clone(),
@@ -84,9 +82,7 @@ pub async fn shutdown(#[context] ctx: RpcContext) -> Result<(), Error> {
#[command(display(display_none))] #[command(display(display_none))]
pub async fn restart(#[context] ctx: RpcContext) -> Result<(), Error> { pub async fn restart(#[context] ctx: RpcContext) -> Result<(), Error> {
let mut db = ctx.db.handle(); let mut db = ctx.db.handle();
crate::db::DatabaseModel::new() crate::db::DatabaseModel::new().lock(&mut db, true).await;
.lock(&mut db, LockType::Write)
.await;
ctx.shutdown ctx.shutdown
.send(Some(Shutdown { .send(Some(Shutdown {
zfs_pool: ctx.zfs_pool_name.clone(), zfs_pool: ctx.zfs_pool_name.clone(),

View File

@@ -3,15 +3,16 @@ use std::sync::Arc;
use anyhow::anyhow; use anyhow::anyhow;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use futures::StreamExt; use futures::future::BoxFuture;
use futures::{FutureExt, StreamExt};
use indexmap::IndexMap; use indexmap::IndexMap;
use patch_db::{DbHandle, HasModel, LockType, Map, MapModel, ModelData}; use patch_db::{DbHandle, HasModel, Map, MapModel, ModelData};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use self::health_check::{HealthCheckId, HealthCheckResult}; use self::health_check::{HealthCheckId, HealthCheckResult};
use crate::context::RpcContext; use crate::context::RpcContext;
use crate::db::model::CurrentDependencyInfo; use crate::db::model::{CurrentDependencyInfo, InstalledPackageDataEntryModel};
use crate::dependencies::DependencyError; use crate::dependencies::{DependencyError, TaggedDependencyError};
use crate::manager::{Manager, Status as ManagerStatus}; use crate::manager::{Manager, Status as ManagerStatus};
use crate::notifications::{notify, NotificationLevel, NotificationSubtype}; use crate::notifications::{notify, NotificationLevel, NotificationSubtype};
use crate::s9pk::manifest::{Manifest, PackageId}; use crate::s9pk::manifest::{Manifest, PackageId};
@@ -22,37 +23,31 @@ pub mod health_check;
// Assume docker for now // Assume docker for now
pub async fn synchronize_all(ctx: &RpcContext) -> Result<(), Error> { pub async fn synchronize_all(ctx: &RpcContext) -> Result<(), Error> {
let mut pkg_ids = crate::db::DatabaseModel::new() let pkg_ids = crate::db::DatabaseModel::new()
.package_data() .package_data()
.keys(&mut ctx.db.handle(), false) .keys(&mut ctx.db.handle(), false)
.await?; .await?;
// TODO: parallelize this futures::stream::iter(pkg_ids)
for id in pkg_ids { .for_each_concurrent(None, |id| async move {
async fn status(ctx: &RpcContext, id: PackageId) -> Result<(), Error> { async fn status(ctx: &RpcContext, id: PackageId) -> Result<(), Error> {
let mut db = ctx.db.handle(); let mut db = ctx.db.handle();
// TODO: DRAGONS!! // TODO: DRAGONS!!
// this locks all of package data to solve a deadlock issue below. As of the writing of this comment, it // this locks all of package data to solve a deadlock issue below. As of the writing of this comment, it
// hangs during the `check` operation on /package-data/<id>. There is another daemon loop somewhere that // hangs during the `check` operation on /package-data/<id>. There is another daemon loop somewhere that
// is likely iterating through packages in a different order. // is likely iterating through packages in a different order.
crate::db::DatabaseModel::new() // crate::db::DatabaseModel::new()
.package_data() // .package_data()
.lock(&mut db, LockType::Write) // .lock(&mut db)
.await; // .await;
// Without the above lock, the below check operation will deadlock // Without the above lock, the below check operation will deadlock
let model = crate::db::DatabaseModel::new() let (mut status, manager) = if let Some(installed) = crate::db::DatabaseModel::new()
.package_data() .package_data()
.idx_model(&id) .idx_model(&id)
.check(&mut db) .and_then(|m| m.installed())
.await? .check(&mut db)
.ok_or_else(|| { .await?
Error::new( {
anyhow!("PackageDataEntry does not exist"),
crate::ErrorKind::Database,
)
})?;
let (mut status, manager) =
if let Some(installed) = model.installed().check(&mut db).await? {
( (
installed.clone().status().get_mut(&mut db).await?, installed.clone().status().get_mut(&mut db).await?,
ctx.managers ctx.managers
@@ -74,42 +69,41 @@ pub async fn synchronize_all(ctx: &RpcContext) -> Result<(), Error> {
return Ok(()); return Ok(());
}; };
let res = status.main.synchronize(&manager).await?; let res = status.main.synchronize(&manager).await?;
status.save(&mut db).await?; status.save(&mut db).await?;
Ok(res) Ok(res)
} }
if let Err(e) = status(ctx, id.clone()).await { if let Err(e) = status(ctx, id.clone()).await {
log::error!("Error syncronizing status of {}: {}", id, e); log::error!("Error syncronizing status of {}: {}", id, e);
} }
} })
.await;
Ok(()) Ok(())
} }
pub async fn check_all(ctx: &RpcContext) -> Result<(), Error> { pub async fn check_all(ctx: &RpcContext) -> Result<(), Error> {
let mut db = ctx.db.handle(); let mut db = ctx.db.handle();
// TODO: DRAGONS!!
// this locks all of package data to solve a deadlock issue below. As of the writing of this comment, it
// hangs during the `check` operation on /package-data/<id>. There is another daemon loop somewhere that
// is likely iterating through packages in a different order.
let pkg_ids = crate::db::DatabaseModel::new() let pkg_ids = crate::db::DatabaseModel::new()
.package_data() .package_data()
.keys(&mut db, false) .keys(&mut db, true)
.await?; .await?;
let mut status_manifest = Vec::with_capacity(pkg_ids.len()); let mut status_manifest = Vec::with_capacity(pkg_ids.len());
let mut status_deps = Vec::with_capacity(pkg_ids.len()); let mut installed_deps = Vec::with_capacity(pkg_ids.len());
for id in &pkg_ids { for id in &pkg_ids {
let model = crate::db::DatabaseModel::new() if let Some(installed) = crate::db::DatabaseModel::new()
.package_data() .package_data()
.idx_model(id) .idx_model(id)
.and_then(|m| m.installed())
.check(&mut db) .check(&mut db)
.await? .await?
.ok_or_else(|| { {
Error::new(
anyhow!("PackageDataEntry does not exist"),
crate::ErrorKind::Database,
)
})?;
model.lock(&mut db, LockType::Write).await;
if let Some(installed) = model.installed().check(&mut db).await? {
let listed_deps = installed let listed_deps = installed
.clone() .clone()
.manifest() .manifest()
@@ -125,8 +119,8 @@ pub async fn check_all(ctx: &RpcContext) -> Result<(), Error> {
installed.clone().status(), installed.clone().status(),
Arc::new(installed.clone().manifest().get(&mut db, true).await?), Arc::new(installed.clone().manifest().get(&mut db, true).await?),
)); ));
status_deps.push(( installed_deps.push((
installed.clone().status(), installed.clone(),
Arc::new({ Arc::new({
installed installed
.current_dependencies() .current_dependencies()
@@ -168,10 +162,7 @@ pub async fn check_all(ctx: &RpcContext) -> Result<(), Error> {
.for_each_concurrent(None, move |(((status, manifest), id), ctx)| { .for_each_concurrent(None, move |(((status, manifest), id), ctx)| {
let status_sender = status_sender.clone(); let status_sender = status_sender.clone();
async move { async move {
match tokio::spawn(main_status(ctx.clone(), status, manifest, ctx.db.handle())) match main_status(ctx.clone(), status, manifest, ctx.db.handle()).await {
.await
.unwrap()
{
Err(e) => { Err(e) => {
log::error!("Error running main health check for {}: {}", id, e); log::error!("Error running main health check for {}: {}", id, e);
log::debug!("{:?}", e); log::debug!("{:?}", e);
@@ -188,30 +179,74 @@ pub async fn check_all(ctx: &RpcContext) -> Result<(), Error> {
} }
let statuses = Arc::new(statuses); let statuses = Arc::new(statuses);
async fn dependency_status<Db: DbHandle>( async fn dependency_status<Db: DbHandle>(
id: &PackageId,
statuses: Arc<HashMap<PackageId, MainStatus>>, statuses: Arc<HashMap<PackageId, MainStatus>>,
status_model: StatusModel, model: InstalledPackageDataEntryModel,
current_deps: Arc<IndexMap<PackageId, CurrentDependencyInfo>>, current_deps: Arc<IndexMap<PackageId, CurrentDependencyInfo>>,
mut db: Db, mut db: Db,
) -> Result<(), Error> { ) -> Result<(), Error> {
let mut status = status_model.get_mut(&mut db).await?; for (dep_id, dep_info) in &*current_deps {
if let Some(err) = match statuses.get(dep_id) {
status Some(MainStatus::Running { ref health, .. })
.dependency_errors | Some(MainStatus::BackingUp {
.update_health_based(&current_deps, &*statuses) started: Some(_),
.await?; ref health,
}) => {
status.save(&mut db).await?; let mut failures = IndexMap::new();
for check in &dep_info.health_checks {
let res = health
.get(check)
.cloned()
.unwrap_or_else(|| HealthCheckResult {
result: HealthCheckResultVariant::Disabled,
time: Utc::now(),
});
if !matches!(res.result, HealthCheckResultVariant::Success) {
failures.insert(check.clone(), res);
}
}
if !failures.is_empty() {
Some(DependencyError::HealthChecksFailed { failures })
} else {
None
}
}
_ => Some(DependencyError::NotRunning),
} {
handle_broken_dependents(
&mut db,
id,
dep_id,
model.clone(),
err,
&mut IndexMap::new(),
)
.await?;
} else {
let mut errs = model
.clone()
.status()
.dependency_errors()
.get_mut(&mut db)
.await?;
if matches!(
errs.get(dep_id),
Some(DependencyError::HealthChecksFailed { .. })
) {
errs.0.remove(dep_id);
errs.save(&mut db).await?;
}
}
}
Ok(()) Ok(())
} }
futures::stream::iter(status_deps.into_iter().zip(pkg_ids.clone())) futures::stream::iter(installed_deps.into_iter().zip(pkg_ids.clone()))
.for_each_concurrent(None, |((status, deps), id)| { .for_each_concurrent(None, |((installed, deps), id)| {
let statuses = statuses.clone(); let statuses = statuses.clone();
async move { async move {
if let Err(e) = if let Err(e) =
tokio::spawn(dependency_status(statuses, status, deps, ctx.db.handle())) dependency_status(&id, statuses, installed, deps, ctx.db.handle()).await
.await
.unwrap()
{ {
log::error!("Error running dependency health check for {}: {}", id, e); log::error!("Error running dependency health check for {}: {}", id, e);
log::debug!("{:?}", e); log::debug!("{:?}", e);
@@ -231,7 +266,7 @@ pub struct Status {
pub dependency_errors: DependencyErrors, pub dependency_errors: DependencyErrors,
} }
#[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Clone, Deserialize, Serialize, HasModel)]
#[serde(tag = "status")] #[serde(tag = "status")]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub enum MainStatus { pub enum MainStatus {
@@ -405,52 +440,86 @@ impl DependencyErrors {
} }
Ok(DependencyErrors(res)) Ok(DependencyErrors(res))
} }
async fn update_health_based( }
&mut self,
dependencies: &IndexMap<PackageId, CurrentDependencyInfo>, pub fn handle_broken_dependents<'a, Db: DbHandle>(
statuses: &HashMap<PackageId, MainStatus>, db: &'a mut Db,
) -> Result<(), Error> { id: &'a PackageId,
for (dep_id, dep_info) in dependencies { dependency: &'a PackageId,
if matches!( model: InstalledPackageDataEntryModel,
self.get(&dep_id), error: DependencyError,
Some(&DependencyError::NotRunning) breakages: &'a mut IndexMap<PackageId, TaggedDependencyError>,
| Some(&DependencyError::HealthChecksFailed { .. }) ) -> BoxFuture<'a, Result<(), Error>> {
| None async move {
) { let mut status = model.clone().status().get_mut(db).await?;
match statuses.get(dep_id) {
Some(MainStatus::Running { ref health, .. }) let old = status.dependency_errors.0.remove(id);
| Some(MainStatus::BackingUp { let newly_broken = old.is_none();
started: Some(_), status.dependency_errors.0.insert(
ref health, id.clone(),
}) => { if let Some(old) = old {
let mut failures = IndexMap::new(); old.merge_with(error.clone())
for check in &dep_info.health_checks { } else {
let res = error.clone()
health },
.get(check) );
.cloned() if newly_broken {
.unwrap_or_else(|| HealthCheckResult { breakages.insert(
result: HealthCheckResultVariant::Disabled, id.clone(),
time: Utc::now(), TaggedDependencyError {
}); dependency: dependency.clone(),
if !matches!(res.result, HealthCheckResultVariant::Success) { error: error.clone(),
failures.insert(check.clone(), res); },
} );
} if status.main.running() {
if !failures.is_empty() { if model
self.0.insert( .clone()
dep_id.clone(), .manifest()
DependencyError::HealthChecksFailed { failures }, .dependencies()
); .idx_model(dependency)
} .get(db, true)
} .await?
_ => { .into_owned()
self.0.insert(dep_id.clone(), DependencyError::NotRunning); .ok_or_else(|| {
Error::new(
anyhow!("{} not in listed dependencies", dependency),
crate::ErrorKind::Database,
)
})?
.critical
{
status.main.stop();
let dependents = model.current_dependents().get(db, true).await?;
for (dependent, _) in &*dependents {
let dependent_model = crate::db::DatabaseModel::new()
.package_data()
.idx_model(dependent)
.and_then(|pkg| pkg.installed())
.check(db)
.await?
.ok_or_else(|| {
Error::new(
anyhow!("{} is not installed", dependent),
crate::ErrorKind::NotFound,
)
})?;
handle_broken_dependents(
db,
dependent,
id,
dependent_model,
DependencyError::NotRunning,
breakages,
)
.await?;
} }
} }
} }
} }
status.save(db).await?;
Ok(()) Ok(())
} }
.boxed()
} }

View File

@@ -11,7 +11,7 @@ use anyhow::anyhow;
use async_trait::async_trait; use async_trait::async_trait;
use clap::ArgMatches; use clap::ArgMatches;
use digest::Digest; use digest::Digest;
use patch_db::HasModel; use patch_db::{HasModel, Model};
use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value; use serde_json::Value;
use tokio::fs::File; use tokio::fs::File;
@@ -328,7 +328,7 @@ impl<'de> serde::de::Deserialize<'de> for ValuePrimative {
} }
} }
#[derive(Debug, Clone, HasModel)] #[derive(Debug, Clone)]
pub struct Version { pub struct Version {
version: emver::Version, version: emver::Version,
string: String, string: String,
@@ -410,6 +410,9 @@ impl Serialize for Version {
self.string.serialize(serializer) self.string.serialize(serializer)
} }
} }
impl HasModel for Version {
type Model = Model<Version>;
}
#[async_trait] #[async_trait]
pub trait AsyncFileExt: Sized { pub trait AsyncFileExt: Sized {