feat: reworker worker task, allows to change db

now way more modularized and better error checked. allows receiving db
uris from a mpsc channel, and reconnects to them
This commit is contained in:
əlemi 2022-11-05 03:27:12 +01:00
parent f48d1e3682
commit 4345a9e9b9
Signed by: alemi
GPG key ID: A4895B84D311642C

View file

@ -1,7 +1,7 @@
use chrono::Utc; use chrono::Utc;
use sea_orm::{TransactionTrait, DatabaseConnection, EntityTrait, Condition, ColumnTrait, QueryFilter, Set, QueryOrder, Order, ActiveModelTrait, ActiveValue::{NotSet, self}}; use sea_orm::{TransactionTrait, DatabaseConnection, EntityTrait, Condition, ColumnTrait, QueryFilter, Set, QueryOrder, Order, ActiveModelTrait, ActiveValue::{NotSet, self}, Database, DbErr};
use tokio::sync::{watch, mpsc}; use tokio::sync::{watch, mpsc};
use tracing::{info, error}; use tracing::{info, error, warn};
use std::collections::VecDeque; use std::collections::VecDeque;
use crate::data::{entities, FetchError}; use crate::data::{entities, FetchError};
@ -37,6 +37,8 @@ struct AppStateTransmitters {
pub struct AppState { pub struct AppState {
tx: AppStateTransmitters, tx: AppStateTransmitters,
db_uri: mpsc::Receiver<String>,
panels: Vec<entities::panels::Model>, panels: Vec<entities::panels::Model>,
sources: Vec<entities::sources::Model>, sources: Vec<entities::sources::Model>,
metrics: Vec<entities::metrics::Model>, metrics: Vec<entities::metrics::Model>,
@ -53,6 +55,7 @@ pub struct AppState {
cache_age: i64, cache_age: i64,
width: watch::Receiver<i64>, width: watch::Receiver<i64>,
last_width: i64,
view: AppStateView, view: AppStateView,
} }
@ -66,6 +69,7 @@ async fn sleep(t:i64) {
impl AppState { impl AppState {
pub fn new( pub fn new(
width: watch::Receiver<i64>, width: watch::Receiver<i64>,
db_uri: mpsc::Receiver<String>,
interval: i64, interval: i64,
cache_age: i64, cache_age: i64,
) -> Result<AppState, FetchError> { ) -> Result<AppState, FetchError> {
@ -86,6 +90,7 @@ impl AppState {
last_refresh: 0, last_refresh: 0,
points: VecDeque::new(), points: VecDeque::new(),
last_check: 0, last_check: 0,
last_width: 0,
flush: flush_rx, flush: flush_rx,
op: op_rx, op: op_rx,
view: AppStateView { view: AppStateView {
@ -105,6 +110,7 @@ impl AppState {
panel_metric: panel_metric_tx, panel_metric: panel_metric_tx,
}, },
width, width,
db_uri,
interval, interval,
cache_age, cache_age,
}) })
@ -143,7 +149,6 @@ impl AppState {
error!(target: "worker", "Could not send panel-metric update: {:?}", e); error!(target: "worker", "Could not send panel-metric update: {:?}", e);
} }
info!(target: "worker", "Updated panels, sources and metrics");
self.last_refresh = chrono::Utc::now().timestamp(); self.last_refresh = chrono::Utc::now().timestamp();
Ok(()) Ok(())
} }
@ -152,16 +157,7 @@ impl AppState {
chrono::Utc::now().timestamp() - self.last_refresh chrono::Utc::now().timestamp() - self.last_refresh
} }
pub async fn worker(mut self, db: DatabaseConnection, run:watch::Receiver<bool>) { pub async fn parse_op(&mut self, op:BackgroundAction, db: &DatabaseConnection) -> Result<(), DbErr> {
let mut width = *self.width.borrow() * 60; // TODO it's in minutes somewhere...
let mut last = Utc::now().timestamp() - width;
while *run.borrow() {
let now = Utc::now().timestamp();
tokio::select!{
op = self.op.recv() => {
match op {
Some(op) => {
match op { match op {
BackgroundAction::UpdateAllPanels { panels } => { BackgroundAction::UpdateAllPanels { panels } => {
// TODO this is kinda rough, can it be done better? // TODO this is kinda rough, can it be done better?
@ -200,11 +196,9 @@ impl AppState {
ActiveValue::Unchanged(pid) => Some(pid), ActiveValue::Unchanged(pid) => Some(pid),
_ => None, _ => None,
}; };
let op = if panel.id == NotSet { panel.insert(&db) } else { panel.update(&db) }; let op = if panel.id == NotSet { panel.insert(db) } else { panel.update(db) };
op.await?;
// TODO chained if is trashy // TODO chained if is trashy
if let Err(e) = op.await {
error!(target: "worker", "Could not update panel: {:?}", e);
} else {
if let Some(panel_id) = panel_id { if let Some(panel_id) = panel_id {
if let Err(e) = db.transaction::<_, (), sea_orm::DbErr>(|txn| { if let Err(e) = db.transaction::<_, (), sea_orm::DbErr>(|txn| {
Box::pin(async move { Box::pin(async move {
@ -223,18 +217,14 @@ impl AppState {
} else { } else {
self.view.request_flush().await; self.view.request_flush().await;
} }
}
}, },
BackgroundAction::UpdateSource { source } => { BackgroundAction::UpdateSource { source } => {
let op = if source.id == NotSet { source.insert(&db) } else { source.update(&db) }; let op = if source.id == NotSet { source.insert(db) } else { source.update(db) };
if let Err(e) = op.await { op.await?;
error!(target: "worker", "Could not update source: {:?}", e);
} else {
self.view.request_flush().await; self.view.request_flush().await;
}
}, },
BackgroundAction::UpdateMetric { metric } => { BackgroundAction::UpdateMetric { metric } => {
let op = if metric.id == NotSet { metric.insert(&db) } else { metric.update(&db) }; let op = if metric.id == NotSet { metric.insert(db) } else { metric.update(db) };
if let Err(e) = op.await { if let Err(e) = op.await {
error!(target: "worker", "Could not update metric: {:?}", e); error!(target: "worker", "Could not update metric: {:?}", e);
} else { } else {
@ -243,64 +233,46 @@ impl AppState {
}, },
// _ => todo!(), // _ => todo!(),
} }
}, Ok(())
None => {},
} }
},
_ = self.flush.recv() => { pub async fn flush_data(&mut self, db: &DatabaseConnection) -> Result<(), DbErr> {
let now = Utc::now().timestamp(); let now = Utc::now().timestamp();
if let Err(e) = self.fetch(&db).await { self.fetch(db).await?;
error!(target: "worker", "Could not fetch from db: {:?}", e);
}
let new_width = *self.width.borrow() * 60; // TODO it's in minutes somewhere... let new_width = *self.width.borrow() * 60; // TODO it's in minutes somewhere...
self.points = match entities::points::Entity::find() self.points = entities::points::Entity::find()
.filter( .filter(
Condition::all() Condition::all()
.add(entities::points::Column::X.gte((now - new_width) as f64)) .add(entities::points::Column::X.gte((now - new_width) as f64))
.add(entities::points::Column::X.lte(now as f64)) .add(entities::points::Column::X.lte(now as f64))
) )
.order_by(entities::points::Column::X, Order::Asc) .order_by(entities::points::Column::X, Order::Asc)
.all(&db) .all(db)
.await { .await?.into();
Ok(p) => p.into(),
Err(e) => {
error!(target: "worker", "Could not fetch new points: {:?}", e);
continue;
}
};
if let Err(e) = self.tx.points.send(self.points.clone().into()) { if let Err(e) = self.tx.points.send(self.points.clone().into()) {
error!(target: "worker", "Could not send new points: {:?}", e); warn!(target: "worker", "Could not send new points: {:?}", e); // TODO should be an err?
} }
last = Utc::now().timestamp(); self.last_check = Utc::now().timestamp() - *self.width.borrow();
info!(target: "worker", "Reloaded points"); info!(target: "worker", "Reloaded points");
}, Ok(())
_ = sleep(self.cache_age - (now - self.last_refresh)) => {
if let Err(e) = self.fetch(&db).await {
error!(target: "worker", "Could not fetch from db: {:?}", e);
} }
},
_ = sleep(self.interval - (now - self.last_check)) => { pub async fn update_points(&mut self, db: &DatabaseConnection) -> Result<(), DbErr> {
let mut changes = false; let mut changes = false;
let now = Utc::now().timestamp(); let now = Utc::now().timestamp();
let new_width = *self.width.borrow() * 60; // TODO it's in minutes somewhere... let new_width = *self.width.borrow() * 60; // TODO it's in minutes somewhere...
// fetch previous points // fetch previous points
if new_width != width { if new_width != self.last_width {
let mut previous_points = match entities::points::Entity::find() let mut previous_points = entities::points::Entity::find()
.filter( .filter(
Condition::all() Condition::all()
.add(entities::points::Column::X.gte(now - new_width)) .add(entities::points::Column::X.gte(now - new_width))
.add(entities::points::Column::X.lte(now - width)) .add(entities::points::Column::X.lte(now - self.last_width))
) )
.order_by(entities::points::Column::X, Order::Asc) .order_by(entities::points::Column::X, Order::Asc)
.all(&db) .all(db)
.await { .await?;
Ok(p) => p,
Err(e) => {
error!(target: "worker", "Could not fetch previous points: {:?}", e);
continue;
}
};
if previous_points.len() > 0 { if previous_points.len() > 0 {
info!(target: "worker", "Fetched {} previous points", previous_points.len()); info!(target: "worker", "Fetched {} previous points", previous_points.len());
} }
@ -312,24 +284,15 @@ impl AppState {
} }
// fetch new points // fetch new points
let new_points = match entities::points::Entity::find() let new_points = entities::points::Entity::find()
.filter( .filter(
Condition::all() Condition::all()
.add(entities::points::Column::X.gte(last as f64)) .add(entities::points::Column::X.gte(self.last_check as f64))
.add(entities::points::Column::X.lte(now as f64)) .add(entities::points::Column::X.lte(now as f64))
) )
.order_by(entities::points::Column::X, Order::Asc) .order_by(entities::points::Column::X, Order::Asc)
.all(&db) .all(db)
.await { .await?;
Ok(p) => p,
Err(e) => {
error!(target: "worker", "Could not fetch new points: {:?}", e);
continue;
}
};
if new_points.len() > 0 {
info!(target: "worker", "Fetched {} new points", new_points.len());
}
for p in new_points { for p in new_points {
self.points.push_back(p); self.points.push_back(p);
@ -346,16 +309,70 @@ impl AppState {
} }
// update // update
last = now; self.last_width = new_width;
width = new_width;
self.last_check = now; self.last_check = now;
if changes { if changes {
if let Err(e) = self.tx.points.send(self.points.clone().into()) { if let Err(e) = self.tx.points.send(self.points.clone().into()) {
error!(target: "worker", "Could not send changes to main thread: {:?}", e); warn!(target: "worker", "Could not send changes to main thread: {:?}", e);
} }
} }
Ok(())
}
pub async fn worker(mut self, run:watch::Receiver<bool>) {
let mut now;
let first_db_uri = self.db_uri.recv().await.unwrap();
let mut db = Database::connect(first_db_uri.clone()).await.unwrap();
info!(target: "worker", "Connected to '{}'", first_db_uri);
while *run.borrow() {
now = Utc::now().timestamp();
tokio::select!{
res = self.db_uri.recv() => {
match res {
Some(uri) => {
match Database::connect(uri.clone()).await {
Ok(new_db) => {
info!("Connected to '{}'", uri);
db = new_db;
},
Err(e) => error!(target: "worker", "Could not connect to db: {:?}", e),
};
},
None => { error!(target: "worker", "URI channel closed"); break; },
}
}, },
}; res = self.op.recv() => {
match res {
Some(op) => match self.parse_op(op, &db).await {
Ok(()) => { },
Err(e) => error!(target: "worker", "Failed executing operation: {:?}", e),
},
None => { error!(target: "worker", "Operations channel closed"); break; },
}
}
res = self.flush.recv() => {
match res {
Some(()) => match self.flush_data(&db).await {
Ok(()) => { },
Err(e) => error!(target: "worker", "Could not flush away current data: {:?}", e),
},
None => { error!(target: "worker", "Flush channel closed"); break; },
}
},
_ = sleep(self.cache_age - (now - self.last_refresh)) => {
if let Err(e) = self.fetch(&db).await {
error!(target: "worker", "Could not fetch from db: {:?}", e);
}
},
_ = sleep(self.interval - (now - self.last_check)) => {
if let Err(e) = self.update_points(&db).await {
error!(target: "worker", "Could not update points: {:?}", e);
}
}
}
} }
} }
} }