background-jobs/jobs-sled/src/lib.rs

256 lines
7 KiB
Rust
Raw Normal View History

2019-09-22 17:41:13 +00:00
#![deny(missing_docs)]
//! # Background Jobs Sled Storage
//! _An implementation of the Background Jobs Storage trait based on the Sled embedded database_
//!
//! ### Usage
2019-09-22 17:49:28 +00:00
//! ```rust,ignore
2019-09-22 17:41:13 +00:00
//! use background_jobs::{ServerConfig, sled_storage::Storage};
//! use sled_extensions::{ConfigBuilder, Db};
//!
//! let db = Db::start(ConfigBuilder::default().temporary(true).build())?;
2019-09-22 17:49:28 +00:00
//! let storage = Storage::new(db)?;
2019-09-22 17:41:13 +00:00
//! let queue_handle = ServerConfig::new(storage).thread_count(8).start();
//! ```
2021-02-06 16:50:47 +00:00
use actix_rt::task::{spawn_blocking, JoinError};
2021-02-04 18:37:29 +00:00
use background_jobs_core::{JobInfo, Stats};
2019-05-25 23:09:10 +00:00
use chrono::offset::Utc;
2021-02-04 18:37:29 +00:00
use sled::{Db, Tree};
2020-03-22 17:52:43 +00:00
use uuid::Uuid;
2019-05-25 20:32:14 +00:00
2020-03-21 02:31:03 +00:00
/// The error produced by sled storage calls
#[derive(Debug, thiserror::Error)]
pub enum Error {
/// Error in the database
#[error("Error in sled extensions, {0}")]
2021-02-04 18:37:29 +00:00
Sled(#[from] sled::Error),
/// Error storing or retrieving job info
#[error("Error transforming job info, {0}")]
Cbor(#[from] serde_cbor::Error),
2020-03-21 02:31:03 +00:00
/// Error executing db operation
#[error("Blocking operation was canceled")]
Canceled,
}
/// A simple alias for Result<T, Error>
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Clone)]
2019-09-22 17:41:13 +00:00
/// The Sled-backed storage implementation
2021-02-04 18:37:29 +00:00
pub struct Storage {
id: Tree,
jobinfo: Tree,
running: Tree,
running_inverse: Tree,
queue: Tree,
stats: Tree,
2019-09-08 23:59:21 +00:00
db: Db,
}
2020-03-21 02:31:03 +00:00
#[async_trait::async_trait]
2021-02-04 18:37:29 +00:00
impl background_jobs_core::Storage for Storage {
type Error = Error;
2020-03-22 17:52:43 +00:00
async fn generate_id(&self) -> Result<Uuid> {
2020-03-21 02:31:03 +00:00
let this = self.clone();
2021-02-04 18:37:29 +00:00
Ok(spawn_blocking(move || {
let mut uuid;
while {
uuid = Uuid::new_v4();
this.id
.compare_and_swap(
uuid.as_bytes(),
None as Option<&[u8]>,
Some(uuid.as_bytes()),
)?
.is_err()
} {}
Ok(uuid) as Result<Uuid>
2020-03-22 17:52:43 +00:00
})
2021-02-04 18:37:29 +00:00
.await??)
}
2020-03-21 02:31:03 +00:00
async fn save_job(&self, job: JobInfo) -> Result<()> {
let this = self.clone();
2021-02-04 18:37:29 +00:00
Ok(spawn_blocking(move || {
let job_vec = serde_cbor::to_vec(&job)?;
this.jobinfo.insert(job.id().as_bytes(), job_vec)?;
Ok(()) as Result<_>
2020-03-21 02:31:03 +00:00
})
2021-02-04 18:37:29 +00:00
.await??)
}
2020-03-22 17:52:43 +00:00
async fn fetch_job(&self, id: Uuid) -> Result<Option<JobInfo>> {
2020-03-21 02:31:03 +00:00
let this = self.clone();
2019-05-25 21:39:16 +00:00
2021-02-04 18:37:29 +00:00
Ok(spawn_blocking(move || {
if let Some(job_ivec) = this.jobinfo.get(id.as_bytes())? {
let job: JobInfo = serde_cbor::from_slice(&job_ivec)?;
Ok(Some(job)) as Result<_>
} else {
Ok(None)
}
})
.await??)
2020-03-21 02:31:03 +00:00
}
2019-05-25 23:09:10 +00:00
2020-03-21 02:31:03 +00:00
async fn fetch_job_from_queue(&self, queue: &str) -> Result<Option<JobInfo>> {
let this = self.clone();
let queue = queue.to_owned();
2021-02-04 18:37:29 +00:00
Ok(spawn_blocking(move || {
let mut job;
2020-03-21 02:31:03 +00:00
2021-02-04 18:37:29 +00:00
let now = Utc::now();
2020-03-21 02:31:03 +00:00
2021-02-04 18:37:29 +00:00
while {
let job_opt = this
.queue
2020-03-21 02:31:03 +00:00
.iter()
.filter_map(|res| res.ok())
2021-02-04 18:37:29 +00:00
.filter_map(|(id, in_queue)| {
if queue.as_bytes() == in_queue.as_ref() {
Some(id)
} else {
None
}
})
.filter_map(|id| this.jobinfo.get(id).ok())
2020-03-21 02:31:03 +00:00
.filter_map(|opt| opt)
2021-02-04 18:37:29 +00:00
.filter_map(|ivec| serde_cbor::from_slice(&ivec).ok())
.find(|job: &JobInfo| job.is_ready(now) && job.is_pending(now));
2020-03-21 02:31:03 +00:00
2021-02-04 18:37:29 +00:00
job = if let Some(job) = job_opt {
job
} else {
return Ok(None);
};
2020-03-21 02:31:03 +00:00
2021-02-04 18:37:29 +00:00
this.queue.remove(job.id().as_bytes())?.is_none()
} {}
Ok(Some(job)) as Result<Option<JobInfo>>
2020-03-21 02:31:03 +00:00
})
2021-02-04 18:37:29 +00:00
.await??)
2020-03-21 02:31:03 +00:00
}
2020-03-22 17:52:43 +00:00
async fn queue_job(&self, queue: &str, id: Uuid) -> Result<()> {
2020-03-21 02:31:03 +00:00
let this = self.clone();
let queue = queue.to_owned();
2019-05-25 21:39:16 +00:00
2021-02-04 18:37:29 +00:00
Ok(spawn_blocking(move || {
if let Some(runner_id) = this.running_inverse.remove(id.as_bytes())? {
this.running.remove(runner_id)?;
2019-05-25 21:39:16 +00:00
}
2021-02-04 18:37:29 +00:00
this.queue.insert(id.as_bytes(), queue.as_bytes())?;
Ok(()) as Result<_>
2019-05-25 21:39:16 +00:00
})
2021-02-04 18:37:29 +00:00
.await??)
}
2020-03-22 17:52:43 +00:00
async fn run_job(&self, id: Uuid, runner_id: Uuid) -> Result<()> {
2020-03-21 02:31:03 +00:00
let this = self.clone();
2021-02-04 18:37:29 +00:00
Ok(spawn_blocking(move || {
this.queue.remove(id.as_bytes())?;
this.running.insert(runner_id.as_bytes(), id.as_bytes())?;
2020-03-21 02:31:03 +00:00
this.running_inverse
2021-02-04 18:37:29 +00:00
.insert(id.as_bytes(), runner_id.as_bytes())?;
2020-03-21 02:31:03 +00:00
Ok(()) as Result<()>
})
2021-02-04 18:37:29 +00:00
.await??)
}
2020-03-22 17:52:43 +00:00
async fn delete_job(&self, id: Uuid) -> Result<()> {
2020-03-21 02:31:03 +00:00
let this = self.clone();
2021-02-04 18:37:29 +00:00
Ok(spawn_blocking(move || {
this.jobinfo.remove(id.as_bytes())?;
this.queue.remove(id.as_bytes())?;
this.id.remove(id.as_bytes())?;
2021-02-04 18:37:29 +00:00
if let Some(runner_id) = this.running_inverse.remove(id.as_bytes())? {
this.running.remove(runner_id)?;
2020-03-21 02:31:03 +00:00
}
Ok(()) as Result<()>
})
2021-02-04 18:37:29 +00:00
.await??)
}
2020-03-21 02:31:03 +00:00
async fn get_stats(&self) -> Result<Stats> {
let this = self.clone();
2021-02-04 18:37:29 +00:00
let stats = spawn_blocking(move || {
let stats = if let Some(stats_ivec) = this.stats.get("stats")? {
bincode::deserialize(&stats_ivec).unwrap_or_default()
} else {
Stats::default()
};
Ok(stats) as Result<Stats>
})
.await??;
Ok(stats)
}
2020-03-21 02:31:03 +00:00
async fn update_stats<F>(&self, f: F) -> Result<()>
where
2020-03-21 02:31:03 +00:00
F: Fn(Stats) -> Stats + Send + 'static,
{
2020-03-21 02:31:03 +00:00
let this = self.clone();
2021-02-04 18:37:29 +00:00
Ok(spawn_blocking(move || {
2020-03-21 02:31:03 +00:00
this.stats.fetch_and_update("stats", move |opt| {
2021-02-04 18:37:29 +00:00
let stats = if let Some(stats_ivec) = opt {
bincode::deserialize(&stats_ivec).unwrap_or_default()
} else {
Stats::default()
2020-03-21 02:31:03 +00:00
};
2021-02-04 18:37:29 +00:00
let new_stats = (f)(stats);
let stats_vec = bincode::serialize(&new_stats).ok()?;
Some(stats_vec)
2020-03-21 02:31:03 +00:00
})?;
Ok(()) as Result<()>
})
2021-02-04 18:37:29 +00:00
.await??)
}
}
2021-02-04 18:37:29 +00:00
impl Storage {
2019-09-22 17:41:13 +00:00
/// Create a new Storage struct
2019-09-08 23:59:21 +00:00
pub fn new(db: Db) -> Result<Self> {
2021-02-04 18:37:29 +00:00
Ok(Storage {
id: db.open_tree("background-jobs-id")?,
jobinfo: db.open_tree("background-jobs-jobinfo")?,
running: db.open_tree("background-jobs-running")?,
running_inverse: db.open_tree("background-jobs-running-inverse")?,
queue: db.open_tree("background-jobs-queue")?,
stats: db.open_tree("background-jobs-stats")?,
db,
})
}
2020-03-21 02:31:03 +00:00
}
2021-02-04 18:37:29 +00:00
impl From<JoinError> for Error {
fn from(_: JoinError) -> Self {
Error::Canceled
2020-03-21 02:31:03 +00:00
}
}