mirror of
https://git.asonix.dog/asonix/background-jobs.git
synced 2024-11-25 21:40:59 +00:00
Make background-jobs simpler to use
This commit is contained in:
parent
645041fbac
commit
1f10095269
13 changed files with 261 additions and 77 deletions
45
README.md
45
README.md
|
@ -10,7 +10,7 @@ might not be the best experience.
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix = "0.8"
|
actix = "0.8"
|
||||||
background-jobs = "0.5.1"
|
background-jobs = "0.6.0"
|
||||||
failure = "0.1"
|
failure = "0.1"
|
||||||
futures = "0.1"
|
futures = "0.1"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
|
@ -24,6 +24,7 @@ operation. They implment the `Job`, `serde::Serialize`, and `serde::DeserializeO
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use background_jobs::Job;
|
use background_jobs::Job;
|
||||||
|
use failure::Error;
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
@ -42,6 +43,9 @@ impl MyJob {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Job for MyJob {
|
impl Job for MyJob {
|
||||||
|
type Processor = MyProcessor; // We will define this later
|
||||||
|
type State = ();
|
||||||
|
|
||||||
fn run(self, _: ()) -> Box<dyn Future<Item = (), Error = Error> + Send> {
|
fn run(self, _: ()) -> Box<dyn Future<Item = (), Error = Error> + Send> {
|
||||||
info!("args: {:?}", self);
|
info!("args: {:?}", self);
|
||||||
|
|
||||||
|
@ -71,7 +75,10 @@ impl MyState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Job<MyState> for MyJob {
|
impl Job for MyJob {
|
||||||
|
type Processor = MyProcessor; // We will define this later
|
||||||
|
type State = MyState;
|
||||||
|
|
||||||
fn run(self, state: MyState) -> Box<dyn Future<Item = (), Error = Error> + Send> {
|
fn run(self, state: MyState) -> Box<dyn Future<Item = (), Error = Error> + Send> {
|
||||||
info!("{}: args, {:?}", state.app_name, self);
|
info!("{}: args, {:?}", state.app_name, self);
|
||||||
|
|
||||||
|
@ -92,7 +99,7 @@ const DEFAULT_QUEUE: &'static str = "default";
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct MyProcessor;
|
pub struct MyProcessor;
|
||||||
|
|
||||||
impl Processor<MyState> for MyProcessor {
|
impl Processor for MyProcessor {
|
||||||
// The kind of job this processor should execute
|
// The kind of job this processor should execute
|
||||||
type Job = MyJob;
|
type Job = MyJob;
|
||||||
|
|
||||||
|
@ -128,7 +135,8 @@ spawning new jobs.
|
||||||
|
|
||||||
`background-jobs-actix` on it's own doesn't have a mechanism for storing worker state. This
|
`background-jobs-actix` on it's own doesn't have a mechanism for storing worker state. This
|
||||||
can be implemented manually by implementing the `Storage` trait from `background-jobs-core`,
|
can be implemented manually by implementing the `Storage` trait from `background-jobs-core`,
|
||||||
or the `background-jobs-sled-storage` crate can be used to provide a
|
the in-memory store provided in the `background-jobs-core` crate can be used, or the
|
||||||
|
`background-jobs-sled-storage` crate can be used to provide a
|
||||||
[Sled](https://github.com/spacejam/sled)-backed jobs store.
|
[Sled](https://github.com/spacejam/sled)-backed jobs store.
|
||||||
|
|
||||||
With that out of the way, back to the examples:
|
With that out of the way, back to the examples:
|
||||||
|
@ -136,7 +144,7 @@ With that out of the way, back to the examples:
|
||||||
##### Main
|
##### Main
|
||||||
```rust
|
```rust
|
||||||
use actix::System;
|
use actix::System;
|
||||||
use background_jobs::{ServerConfig, SledStorage, WorkerConfig};
|
use background_jobs::{ServerConfig, WorkerConfig};
|
||||||
use failure::Error;
|
use failure::Error;
|
||||||
|
|
||||||
fn main() -> Result<(), Error> {
|
fn main() -> Result<(), Error> {
|
||||||
|
@ -144,22 +152,31 @@ fn main() -> Result<(), Error> {
|
||||||
let sys = System::new("my-actix-system");
|
let sys = System::new("my-actix-system");
|
||||||
|
|
||||||
// Set up our Storage
|
// Set up our Storage
|
||||||
|
// For this example, we use the default in-memory storage mechanism
|
||||||
|
use background_jobs::memory_storage::Storage;
|
||||||
|
let storage = Storage::new();
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Optionally, a storage backend using the Sled database is provided
|
||||||
|
use sled::Db;
|
||||||
|
use background_jobs::sled_storage::Storage;
|
||||||
let db = Db::start_default("my-sled-db")?;
|
let db = Db::start_default("my-sled-db")?;
|
||||||
let storage = SledStorage::new(db)?;
|
let storage = Storage::new(db)?;
|
||||||
|
*/
|
||||||
|
|
||||||
// Start the application server. This guards access to to the jobs store
|
// Start the application server. This guards access to to the jobs store
|
||||||
let queue_handle = ServerConfig::new(storage).start();
|
let queue_handle = ServerConfig::new(storage).thread_count(8).start();
|
||||||
|
|
||||||
// Configure and start our workers
|
// Configure and start our workers
|
||||||
let mut worker_config = WorkerConfig::new(move || MyState::new("My App"));
|
WorkerConfig::new(move || MyState::new("My App"))
|
||||||
worker_config.register(MyProcessor);
|
.register(MyProcessor(queue_handle.clone()))
|
||||||
worker_config.set_processor_count(DEFAULT_QUEUE, 16);
|
.set_processor_count(DEFAULT_QUEUE, 16)
|
||||||
worker_config.start(queue_handle.clone());
|
.start(queue_handle.clone());
|
||||||
|
|
||||||
// Queue our jobs
|
// Queue our jobs
|
||||||
queue_handle.queue::<MyProcessor>(MyJob::new(1, 2))?;
|
queue_handle.queue(MyJob::new(1, 2))?;
|
||||||
queue_handle.queue::<MyProcessor>(MyJob::new(3, 4))?;
|
queue_handle.queue(MyJob::new(3, 4))?;
|
||||||
queue_handle.queue::<MyProcessor>(MyJob::new(5, 6))?;
|
queue_handle.queue(MyJob::new(5, 6))?;
|
||||||
|
|
||||||
// Block on Actix
|
// Block on Actix
|
||||||
sys.run()?;
|
sys.run()?;
|
||||||
|
|
4
TODO
4
TODO
|
@ -1,4 +0,0 @@
|
||||||
1.
|
|
||||||
Gracefull Shutdown
|
|
||||||
2.
|
|
||||||
Periodically check staged jobs
|
|
|
@ -1,11 +1,10 @@
|
||||||
use actix::System;
|
use actix::System;
|
||||||
use background_jobs::{
|
use background_jobs::{
|
||||||
Backoff, Job, MaxRetries, Processor, ServerConfig, SledStorage, WorkerConfig, QueueHandle,
|
Backoff, Job, MaxRetries, Processor, QueueHandle, ServerConfig, WorkerConfig,
|
||||||
};
|
};
|
||||||
use failure::Error;
|
use failure::Error;
|
||||||
use futures::{future::ok, Future};
|
use futures::{future::ok, Future};
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use sled::Db;
|
|
||||||
|
|
||||||
const DEFAULT_QUEUE: &'static str = "default";
|
const DEFAULT_QUEUE: &'static str = "default";
|
||||||
|
|
||||||
|
@ -24,24 +23,37 @@ pub struct MyJob {
|
||||||
pub struct MyProcessor(pub QueueHandle);
|
pub struct MyProcessor(pub QueueHandle);
|
||||||
|
|
||||||
fn main() -> Result<(), Error> {
|
fn main() -> Result<(), Error> {
|
||||||
|
// First set up the Actix System to ensure we have a runtime to spawn jobs on.
|
||||||
let sys = System::new("my-actix-system");
|
let sys = System::new("my-actix-system");
|
||||||
|
|
||||||
|
// Set up our Storage
|
||||||
|
// For this example, we use the default in-memory storage mechanism
|
||||||
|
use background_jobs::memory_storage::Storage;
|
||||||
|
let storage = Storage::new();
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Optionally, a storage backend using the Sled database is provided
|
||||||
|
use sled::Db;
|
||||||
|
use background_jobs::sled_storage::Storage;
|
||||||
let db = Db::start_default("my-sled-db")?;
|
let db = Db::start_default("my-sled-db")?;
|
||||||
let storage = SledStorage::new(db)?;
|
let storage = Storage::new(db)?;
|
||||||
|
*/
|
||||||
|
|
||||||
let queue_handle = ServerConfig::new(storage).thread_count(2).start();
|
// Start the application server. This guards access to to the jobs store
|
||||||
|
let queue_handle = ServerConfig::new(storage).thread_count(8).start();
|
||||||
let processor = MyProcessor(queue_handle.clone());
|
|
||||||
|
|
||||||
|
// Configure and start our workers
|
||||||
WorkerConfig::new(move || MyState::new("My App"))
|
WorkerConfig::new(move || MyState::new("My App"))
|
||||||
.register(processor.clone())
|
.register(MyProcessor(queue_handle.clone()))
|
||||||
.set_processor_count(DEFAULT_QUEUE, 16)
|
.set_processor_count(DEFAULT_QUEUE, 16)
|
||||||
.start(queue_handle.clone());
|
.start(queue_handle.clone());
|
||||||
|
|
||||||
processor.queue(MyJob::new(1, 2))?;
|
// Queue our jobs
|
||||||
processor.queue(MyJob::new(3, 4))?;
|
queue_handle.queue(MyJob::new(1, 2))?;
|
||||||
processor.queue(MyJob::new(5, 6))?;
|
queue_handle.queue(MyJob::new(3, 4))?;
|
||||||
|
queue_handle.queue(MyJob::new(5, 6))?;
|
||||||
|
|
||||||
|
// Block on Actix
|
||||||
sys.run()?;
|
sys.run()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -63,7 +75,10 @@ impl MyJob {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Job<MyState> for MyJob {
|
impl Job for MyJob {
|
||||||
|
type Processor = MyProcessor;
|
||||||
|
type State = MyState;
|
||||||
|
|
||||||
fn run(self, state: MyState) -> Box<dyn Future<Item = (), Error = Error> + Send> {
|
fn run(self, state: MyState) -> Box<dyn Future<Item = (), Error = Error> + Send> {
|
||||||
println!("{}: args, {:?}", state.app_name, self);
|
println!("{}: args, {:?}", state.app_name, self);
|
||||||
|
|
||||||
|
@ -71,13 +86,7 @@ impl Job<MyState> for MyJob {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MyProcessor {
|
impl Processor for MyProcessor {
|
||||||
fn queue(&self, job: <Self as Processor<MyState>>::Job) -> Result<(), Error> {
|
|
||||||
self.0.queue::<Self, _>(job)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Processor<MyState> for MyProcessor {
|
|
||||||
// The kind of job this processor should execute
|
// The kind of job this processor should execute
|
||||||
type Job = MyJob;
|
type Job = MyJob;
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ failure = "0.1"
|
||||||
futures = "0.1"
|
futures = "0.1"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
num_cpus = "1.10.0"
|
num_cpus = "1.10.0"
|
||||||
|
rand = "0.6.5"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::{collections::BTreeMap, sync::Arc};
|
use std::{collections::BTreeMap, sync::Arc};
|
||||||
|
|
||||||
use actix::{Actor, Addr, SyncArbiter};
|
use actix::{Actor, Addr, SyncArbiter};
|
||||||
use background_jobs_core::{Processor, ProcessorMap, Stats, Storage};
|
use background_jobs_core::{Job, Processor, ProcessorMap, Stats, Storage};
|
||||||
use failure::Error;
|
use failure::Error;
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ where
|
||||||
Server::new(StorageWrapper(storage.clone()))
|
Server::new(StorageWrapper(storage.clone()))
|
||||||
});
|
});
|
||||||
|
|
||||||
Pinger::new(server.clone()).start();
|
Pinger::new(server.clone(), threads).start();
|
||||||
|
|
||||||
QueueHandle { inner: server }
|
QueueHandle { inner: server }
|
||||||
}
|
}
|
||||||
|
@ -91,11 +91,11 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register<P>(mut self, processor: P) -> Self
|
pub fn register(
|
||||||
where
|
mut self,
|
||||||
P: Processor<State> + Send + 'static,
|
processor: impl Processor<Job = impl Job<State = State> + Send + 'static> + Send + 'static,
|
||||||
{
|
) -> Self {
|
||||||
self.queues.insert(P::QUEUE.to_owned(), 4);
|
self.queues.insert(processor.queue().to_owned(), 4);
|
||||||
self.processors.register_processor(processor);
|
self.processors.register_processor(processor);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -130,12 +130,11 @@ pub struct QueueHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QueueHandle {
|
impl QueueHandle {
|
||||||
pub fn queue<P, State>(&self, job: P::Job) -> Result<(), Error>
|
pub fn queue<J>(&self, job: J) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
P: Processor<State>,
|
J: Job,
|
||||||
State: Clone,
|
|
||||||
{
|
{
|
||||||
self.inner.do_send(NewJob(P::new_job(job)?));
|
self.inner.do_send(NewJob(J::Processor::new_job(job)?));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,12 @@ use crate::{CheckDb, Server};
|
||||||
|
|
||||||
pub struct Pinger {
|
pub struct Pinger {
|
||||||
server: Addr<Server>,
|
server: Addr<Server>,
|
||||||
|
threads: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pinger {
|
impl Pinger {
|
||||||
pub fn new(server: Addr<Server>) -> Self {
|
pub fn new(server: Addr<Server>, threads: usize) -> Self {
|
||||||
Pinger { server }
|
Pinger { server, threads }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +19,9 @@ impl Actor for Pinger {
|
||||||
|
|
||||||
fn started(&mut self, ctx: &mut Self::Context) {
|
fn started(&mut self, ctx: &mut Self::Context) {
|
||||||
ctx.run_interval(Duration::from_secs(1), |actor, _| {
|
ctx.run_interval(Duration::from_secs(1), |actor, _| {
|
||||||
|
for _ in 0..actor.threads {
|
||||||
actor.server.do_send(CheckDb);
|
actor.server.do_send(CheckDb);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@ impl Message for RequestJob {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Message for CheckDb {
|
impl Message for CheckDb {
|
||||||
type Result = Result<(), Error>;
|
type Result = ();
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Message for GetStats {
|
impl Message for GetStats {
|
||||||
|
@ -70,7 +70,7 @@ impl Handler<NewJob> for Server {
|
||||||
let entry = self.cache.entry(queue.clone()).or_insert(VecDeque::new());
|
let entry = self.cache.entry(queue.clone()).or_insert(VecDeque::new());
|
||||||
|
|
||||||
if let Some(worker) = entry.pop_front() {
|
if let Some(worker) = entry.pop_front() {
|
||||||
if let Some(job) = self.storage.request_job(&queue, worker.id())? {
|
if let Ok(Some(job)) = self.storage.request_job(&queue, worker.id()) {
|
||||||
worker.process_job(job);
|
worker.process_job(job);
|
||||||
} else {
|
} else {
|
||||||
entry.push_back(worker);
|
entry.push_back(worker);
|
||||||
|
@ -117,7 +117,7 @@ impl Handler<RequestJob> for Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler<CheckDb> for Server {
|
impl Handler<CheckDb> for Server {
|
||||||
type Result = Result<(), Error>;
|
type Result = ();
|
||||||
|
|
||||||
fn handle(&mut self, _: CheckDb, _: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, _: CheckDb, _: &mut Self::Context) -> Self::Result {
|
||||||
trace!("Checkdb");
|
trace!("Checkdb");
|
||||||
|
@ -125,7 +125,7 @@ impl Handler<CheckDb> for Server {
|
||||||
for (queue, workers) in self.cache.iter_mut() {
|
for (queue, workers) in self.cache.iter_mut() {
|
||||||
while !workers.is_empty() {
|
while !workers.is_empty() {
|
||||||
if let Some(worker) = workers.pop_front() {
|
if let Some(worker) = workers.pop_front() {
|
||||||
if let Some(job) = self.storage.request_job(queue, worker.id())? {
|
if let Ok(Some(job)) = self.storage.request_job(queue, worker.id()) {
|
||||||
worker.process_job(job);
|
worker.process_job(job);
|
||||||
} else {
|
} else {
|
||||||
workers.push_back(worker);
|
workers.push_back(worker);
|
||||||
|
@ -134,8 +134,6 @@ impl Handler<CheckDb> for Server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,13 +21,17 @@ use failure::Error;
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use serde::{de::DeserializeOwned, ser::Serialize};
|
use serde::{de::DeserializeOwned, ser::Serialize};
|
||||||
|
|
||||||
use crate::{Backoff, MaxRetries};
|
use crate::{Backoff, MaxRetries, Processor};
|
||||||
|
|
||||||
/// The Job trait defines parameters pertaining to an instance of background job
|
/// The Job trait defines parameters pertaining to an instance of background job
|
||||||
pub trait Job<S = ()>: Serialize + DeserializeOwned
|
pub trait Job: Serialize + DeserializeOwned + 'static {
|
||||||
where
|
/// The processor this job is associated with. The job's processor can be used to create a
|
||||||
S: Clone + 'static,
|
/// JobInfo from a job, which is used to serialize the job into a storage mechanism.
|
||||||
{
|
type Processor: Processor<Job = Self>;
|
||||||
|
|
||||||
|
/// The application state provided to this job at runtime.
|
||||||
|
type State: Clone + 'static;
|
||||||
|
|
||||||
/// Users of this library must define what it means to run a job.
|
/// Users of this library must define what it means to run a job.
|
||||||
///
|
///
|
||||||
/// This should contain all the logic needed to complete a job. If that means queuing more
|
/// This should contain all the logic needed to complete a job. If that means queuing more
|
||||||
|
@ -37,7 +41,7 @@ where
|
||||||
/// The state passed into this job is initialized at the start of the application. The state
|
/// The state passed into this job is initialized at the start of the application. The state
|
||||||
/// argument could be useful for containing a hook into something like r2d2, or the address of
|
/// argument could be useful for containing a hook into something like r2d2, or the address of
|
||||||
/// an actor in an actix-based system.
|
/// an actor in an actix-based system.
|
||||||
fn run(self, state: S) -> Box<dyn Future<Item = (), Error = Error> + Send>;
|
fn run(self, state: Self::State) -> Box<dyn Future<Item = (), Error = Error> + Send>;
|
||||||
|
|
||||||
/// If this job should not use the default queue for its processor, this can be overridden in
|
/// If this job should not use the default queue for its processor, this can be overridden in
|
||||||
/// user-code.
|
/// user-code.
|
||||||
|
|
|
@ -33,7 +33,7 @@ pub use crate::{
|
||||||
processor::Processor,
|
processor::Processor,
|
||||||
processor_map::ProcessorMap,
|
processor_map::ProcessorMap,
|
||||||
stats::{JobStat, Stats},
|
stats::{JobStat, Stats},
|
||||||
storage::Storage,
|
storage::{memory_storage, Storage},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Fail)]
|
#[derive(Debug, Fail)]
|
||||||
|
|
|
@ -81,11 +81,8 @@ use crate::{Backoff, Job, JobError, MaxRetries, NewJobInfo};
|
||||||
/// Ok(())
|
/// Ok(())
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub trait Processor<S = ()>: Clone
|
pub trait Processor: Clone {
|
||||||
where
|
type Job: Job + 'static;
|
||||||
S: Clone + 'static,
|
|
||||||
{
|
|
||||||
type Job: Job<S> + 'static;
|
|
||||||
|
|
||||||
/// The name of the processor
|
/// The name of the processor
|
||||||
///
|
///
|
||||||
|
@ -175,7 +172,7 @@ where
|
||||||
fn process(
|
fn process(
|
||||||
&self,
|
&self,
|
||||||
args: Value,
|
args: Value,
|
||||||
state: S,
|
state: <Self::Job as Job>::State,
|
||||||
) -> Box<dyn Future<Item = (), Error = JobError> + Send> {
|
) -> Box<dyn Future<Item = (), Error = JobError> + Send> {
|
||||||
let res = serde_json::from_value::<Self::Job>(args);
|
let res = serde_json::from_value::<Self::Job>(args);
|
||||||
|
|
||||||
|
@ -186,6 +183,16 @@ where
|
||||||
|
|
||||||
Box::new(fut)
|
Box::new(fut)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Hack to access Associated Constant from impl type
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
Self::NAME
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Hack to access Associated Constant from impl type
|
||||||
|
fn queue(&self) -> &'static str {
|
||||||
|
Self::QUEUE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Fail)]
|
#[derive(Clone, Debug, Fail)]
|
||||||
|
|
|
@ -23,7 +23,7 @@ use futures::future::{Either, Future, IntoFuture};
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use crate::{JobError, JobInfo, Processor, ReturnJobInfo};
|
use crate::{Job, JobError, JobInfo, Processor, ReturnJobInfo};
|
||||||
|
|
||||||
/// A generic function that processes a job
|
/// A generic function that processes a job
|
||||||
///
|
///
|
||||||
|
@ -72,12 +72,12 @@ where
|
||||||
///
|
///
|
||||||
/// `ProcessorMap`s are useless if no processors are registerd before workers are spawned, so
|
/// `ProcessorMap`s are useless if no processors are registerd before workers are spawned, so
|
||||||
/// make sure to register all your processors up-front.
|
/// make sure to register all your processors up-front.
|
||||||
pub fn register_processor<P>(&mut self, processor: P)
|
pub fn register_processor(
|
||||||
where
|
&mut self,
|
||||||
P: Processor<S> + Send + 'static,
|
processor: impl Processor<Job = impl Job<State = S> + 'static> + Send + 'static,
|
||||||
{
|
) {
|
||||||
self.inner.insert(
|
self.inner.insert(
|
||||||
P::NAME.to_owned(),
|
processor.name().to_owned(),
|
||||||
Box::new(move |value, state| processor.process(value, state)),
|
Box::new(move |value, state| processor.process(value, state)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,14 @@ use log::error;
|
||||||
|
|
||||||
use crate::{JobInfo, NewJobInfo, ReturnJobInfo, Stats};
|
use crate::{JobInfo, NewJobInfo, ReturnJobInfo, Stats};
|
||||||
|
|
||||||
|
/// Define a storage backend for jobs
|
||||||
|
///
|
||||||
|
/// This crate provides a default implementation in the `memory_storage` module, which is backed by
|
||||||
|
/// HashMaps and uses counting to assign IDs. If jobs must be persistent across application
|
||||||
|
/// restarts, look into the `[sled-backed](https://github.com/spacejam/sled)` implementation from
|
||||||
|
/// the `background-jobs-sled-storage` crate.
|
||||||
pub trait Storage: Clone + Send {
|
pub trait Storage: Clone + Send {
|
||||||
|
/// The error type used by the storage mechansim.
|
||||||
type Error: Fail;
|
type Error: Fail;
|
||||||
|
|
||||||
/// This method generates unique IDs for jobs
|
/// This method generates unique IDs for jobs
|
||||||
|
@ -132,3 +139,142 @@ pub trait Storage: Clone + Send {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod memory_storage {
|
||||||
|
use super::{JobInfo, Stats};
|
||||||
|
use failure::Fail;
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
fmt,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Storage {
|
||||||
|
inner: Arc<Mutex<Inner>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct Inner {
|
||||||
|
count: u64,
|
||||||
|
jobs: HashMap<u64, JobInfo>,
|
||||||
|
queues: HashMap<u64, String>,
|
||||||
|
worker_ids: HashMap<u64, u64>,
|
||||||
|
worker_ids_inverse: HashMap<u64, u64>,
|
||||||
|
stats: Stats,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Storage {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Storage {
|
||||||
|
inner: Arc::new(Mutex::new(Inner {
|
||||||
|
count: 0,
|
||||||
|
jobs: HashMap::new(),
|
||||||
|
queues: HashMap::new(),
|
||||||
|
worker_ids: HashMap::new(),
|
||||||
|
worker_ids_inverse: HashMap::new(),
|
||||||
|
stats: Stats::default(),
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl super::Storage for Storage {
|
||||||
|
type Error = Never;
|
||||||
|
|
||||||
|
fn generate_id(&mut self) -> Result<u64, Self::Error> {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
let id = inner.count;
|
||||||
|
inner.count = inner.count.wrapping_add(1);
|
||||||
|
Ok(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_job(&mut self, job: JobInfo) -> Result<(), Self::Error> {
|
||||||
|
self.inner.lock().unwrap().jobs.insert(job.id(), job);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fetch_job(&mut self, id: u64) -> Result<Option<JobInfo>, Self::Error> {
|
||||||
|
let j = self.inner.lock().unwrap().jobs.get(&id).map(|j| j.clone());
|
||||||
|
|
||||||
|
Ok(j)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fetch_job_from_queue(&mut self, queue: &str) -> Result<Option<JobInfo>, Self::Error> {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
|
||||||
|
let j = inner
|
||||||
|
.queues
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(k, v)| {
|
||||||
|
if v == queue {
|
||||||
|
inner.jobs.get(k).map(|j| j.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.next();
|
||||||
|
|
||||||
|
if let Some(ref j) = j {
|
||||||
|
inner.queues.remove(&j.id());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(j)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn queue_job(&mut self, queue: &str, id: u64) -> Result<(), Self::Error> {
|
||||||
|
self.inner
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.queues
|
||||||
|
.insert(id, queue.to_owned());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_job(&mut self, id: u64, worker_id: u64) -> Result<(), Self::Error> {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
|
||||||
|
inner.worker_ids.insert(id, worker_id);
|
||||||
|
inner.worker_ids_inverse.insert(worker_id, id);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_job(&mut self, id: u64) -> Result<(), Self::Error> {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
inner.jobs.remove(&id);
|
||||||
|
inner.queues.remove(&id);
|
||||||
|
if let Some(worker_id) = inner.worker_ids.remove(&id) {
|
||||||
|
inner.worker_ids_inverse.remove(&worker_id);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_stats(&self) -> Result<Stats, Self::Error> {
|
||||||
|
Ok(self.inner.lock().unwrap().stats.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_stats<F>(&mut self, f: F) -> Result<(), Self::Error>
|
||||||
|
where
|
||||||
|
F: Fn(Stats) -> Stats,
|
||||||
|
{
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
|
||||||
|
inner.stats = (f)(inner.stats.clone());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Fail)]
|
||||||
|
pub enum Never {}
|
||||||
|
|
||||||
|
impl fmt::Display for Never {
|
||||||
|
fn fmt(&self, _: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match *self {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Fail)]
|
||||||
|
#[fail(display = "Created too many storages, can't generate any more IDs")]
|
||||||
|
pub struct TooManyStoragesError;
|
||||||
|
}
|
||||||
|
|
|
@ -197,10 +197,14 @@
|
||||||
//! `background-jobs-core` crate, which provides the Processor and Job traits, as well as some
|
//! `background-jobs-core` crate, which provides the Processor and Job traits, as well as some
|
||||||
//! other useful types for implementing a jobs processor and job store.
|
//! other useful types for implementing a jobs processor and job store.
|
||||||
|
|
||||||
pub use background_jobs_core::{Backoff, Job, JobStat, MaxRetries, Processor, Stats};
|
pub use background_jobs_core::{
|
||||||
|
memory_storage, Backoff, Job, JobStat, MaxRetries, Processor, Stats,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(feature = "background-jobs-actix")]
|
#[cfg(feature = "background-jobs-actix")]
|
||||||
pub use background_jobs_actix::{QueueHandle, ServerConfig, WorkerConfig};
|
pub use background_jobs_actix::{QueueHandle, ServerConfig, WorkerConfig};
|
||||||
|
|
||||||
#[cfg(feature = "background-jobs-sled-storage")]
|
#[cfg(feature = "background-jobs-sled-storage")]
|
||||||
pub use background_jobs_sled_storage::{Error as SledStorageError, SledStorage};
|
pub mod sled_storage {
|
||||||
|
pub use background_jobs_sled_storage::{Error, SledStorage as Storage};
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue