mirror of
https://git.asonix.dog/asonix/background-jobs.git
synced 2024-11-25 05:21:00 +00:00
Eliminate Processor
This commit is contained in:
parent
ca1c073666
commit
759ccf018b
18 changed files with 319 additions and 442 deletions
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "background-jobs"
|
name = "background-jobs"
|
||||||
description = "Background Jobs implemented with sled, actix, and futures"
|
description = "Background Jobs implemented with sled, actix, and futures"
|
||||||
version = "0.8.0-alpha.0"
|
version = "0.8.0-alpha.1"
|
||||||
license-file = "LICENSE"
|
license-file = "LICENSE"
|
||||||
authors = ["asonix <asonix@asonix.dog>"]
|
authors = ["asonix <asonix@asonix.dog>"]
|
||||||
repository = "https://git.asonix.dog/Aardwolf/background-jobs"
|
repository = "https://git.asonix.dog/Aardwolf/background-jobs"
|
||||||
|
@ -21,15 +21,15 @@ members = [
|
||||||
default = ["background-jobs-actix", "background-jobs-sled-storage"]
|
default = ["background-jobs-actix", "background-jobs-sled-storage"]
|
||||||
|
|
||||||
[dependencies.background-jobs-core]
|
[dependencies.background-jobs-core]
|
||||||
version = "0.7.0"
|
version = "0.8.0-alpha.0"
|
||||||
path = "jobs-core"
|
path = "jobs-core"
|
||||||
|
|
||||||
[dependencies.background-jobs-actix]
|
[dependencies.background-jobs-actix]
|
||||||
version = "0.7.0-alpha.0"
|
version = "0.8.0-alpha.0"
|
||||||
path = "jobs-actix"
|
path = "jobs-actix"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.background-jobs-sled-storage]
|
[dependencies.background-jobs-sled-storage]
|
||||||
version = "0.4.0-alpha.0"
|
version = "0.8.0-alpha.0"
|
||||||
path = "jobs-sled"
|
path = "jobs-sled"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
67
README.md
67
README.md
|
@ -7,14 +7,14 @@ might not be the best experience.
|
||||||
|
|
||||||
- [Read the documentation on docs.rs](https://docs.rs/background-jobs)
|
- [Read the documentation on docs.rs](https://docs.rs/background-jobs)
|
||||||
- [Find the crate on crates.io](https://crates.io/crates/background-jobs)
|
- [Find the crate on crates.io](https://crates.io/crates/background-jobs)
|
||||||
- [Join the discussion on Matrix](https://matrix.to/#/!vZKoAKLpHaFIWjRxpT:asonix.dog?via=asonix.dog)
|
- [Hit me up on Mastodon](https://asonix.dog/@asonix)
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
#### Add Background Jobs to your project
|
#### Add Background Jobs to your project
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix = "0.8"
|
actix = "0.8"
|
||||||
background-jobs = "0.7.0"
|
background-jobs = "0.8.0-alpha.1"
|
||||||
failure = "0.1"
|
failure = "0.1"
|
||||||
futures = "0.1"
|
futures = "0.1"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
|
@ -47,10 +47,11 @@ impl MyJob {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Job for MyJob {
|
impl Job for MyJob {
|
||||||
type Processor = MyProcessor; // We will define this later
|
|
||||||
type State = ();
|
type State = ();
|
||||||
type Future = Result<(), Error>;
|
type Future = Result<(), Error>;
|
||||||
|
|
||||||
|
const NAME: &'static str = "MyJob";
|
||||||
|
|
||||||
fn run(self, _: Self::State) -> Self::Future {
|
fn run(self, _: Self::State) -> Self::Future {
|
||||||
info!("args: {:?}", self);
|
info!("args: {:?}", self);
|
||||||
|
|
||||||
|
@ -81,38 +82,13 @@ impl MyState {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Job for MyJob {
|
impl Job for MyJob {
|
||||||
type Processor = MyProcessor; // We will define this later
|
|
||||||
type State = MyState;
|
type State = MyState;
|
||||||
type Future = Result<(), Error>;
|
type Future = Result<(), Error>;
|
||||||
|
|
||||||
fn run(self, state: Self::State) -> Self::Future {
|
// The name of the job. It is super important that each job has a unique name,
|
||||||
info!("{}: args, {:?}", state.app_name, self);
|
// because otherwise one job will overwrite another job when they're being
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Next, define a Processor.
|
|
||||||
Processors are types that define default attributes for jobs, as well as containing some logic
|
|
||||||
used internally to perform the job. Processors must implement `Proccessor` and `Clone`.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use background_jobs::{Backoff, MaxRetries, Processor};
|
|
||||||
|
|
||||||
const DEFAULT_QUEUE: &'static str = "default";
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct MyProcessor;
|
|
||||||
|
|
||||||
impl Processor for MyProcessor {
|
|
||||||
// The kind of job this processor should execute
|
|
||||||
type Job = MyJob;
|
|
||||||
|
|
||||||
// The name of the processor. It is super important that each processor has a unique name,
|
|
||||||
// because otherwise one processor will overwrite another processor when they're being
|
|
||||||
// registered.
|
// registered.
|
||||||
const NAME: &'static str = "MyProcessor";
|
const NAME: &'static str = "MyJob";
|
||||||
|
|
||||||
// The queue that this processor belongs to
|
// The queue that this processor belongs to
|
||||||
//
|
//
|
||||||
|
@ -130,7 +106,13 @@ impl Processor for MyProcessor {
|
||||||
// The logic to determine how often to retry this job if it fails
|
// The logic to determine how often to retry this job if it fails
|
||||||
//
|
//
|
||||||
// Jobs can optionally override this value
|
// Jobs can optionally override this value
|
||||||
const BACKOFF_STRATEGY: Backoff = Backoff::Exponential(2);
|
const BACKOFF: Backoff = Backoff::Exponential(2);
|
||||||
|
|
||||||
|
fn run(self, state: Self::State) -> Self::Future {
|
||||||
|
info!("{}: args, {:?}", state.app_name, self);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -153,10 +135,9 @@ use actix::System;
|
||||||
use background_jobs::{ServerConfig, WorkerConfig};
|
use background_jobs::{ServerConfig, WorkerConfig};
|
||||||
use failure::Error;
|
use failure::Error;
|
||||||
|
|
||||||
fn main() -> Result<(), Error> {
|
#[actix_rt::main]
|
||||||
// First set up the Actix System to ensure we have a runtime to spawn jobs on.
|
async fn main() -> Result<(), Error> {
|
||||||
let sys = System::new("my-actix-system");
|
env_logger::init();
|
||||||
|
|
||||||
// Set up our Storage
|
// Set up our Storage
|
||||||
// For this example, we use the default in-memory storage mechanism
|
// For this example, we use the default in-memory storage mechanism
|
||||||
use background_jobs::memory_storage::Storage;
|
use background_jobs::memory_storage::Storage;
|
||||||
|
@ -164,19 +145,19 @@ fn main() -> Result<(), Error> {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
// Optionally, a storage backend using the Sled database is provided
|
// Optionally, a storage backend using the Sled database is provided
|
||||||
use sled::{ConfigBuilder, Db};
|
|
||||||
use background_jobs::sled_storage::Storage;
|
use background_jobs::sled_storage::Storage;
|
||||||
let db = Db::start(ConfigBuilder::default().temporary(true).build())?;
|
use sled_extensions::Db;
|
||||||
|
let db = Db::open("my-sled-db")?;
|
||||||
let storage = Storage::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).thread_count(8).start();
|
let queue_handle = create_server(storage);
|
||||||
|
|
||||||
// Configure and start our workers
|
// Configure and start our workers
|
||||||
WorkerConfig::new(move || MyState::new("My App"))
|
WorkerConfig::new(move || MyState::new("My App"))
|
||||||
.register(MyProcessor)
|
.register::<MyJob>()
|
||||||
.set_processor_count(DEFAULT_QUEUE, 16)
|
.set_worker_count(DEFAULT_QUEUE, 16)
|
||||||
.start(queue_handle.clone());
|
.start(queue_handle.clone());
|
||||||
|
|
||||||
// Queue our jobs
|
// Queue our jobs
|
||||||
|
@ -185,7 +166,7 @@ fn main() -> Result<(), Error> {
|
||||||
queue_handle.queue(MyJob::new(5, 6))?;
|
queue_handle.queue(MyJob::new(5, 6))?;
|
||||||
|
|
||||||
// Block on Actix
|
// Block on Actix
|
||||||
sys.run()?;
|
actix_rt::signal::ctrl_c().await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -195,7 +176,7 @@ For the complete example project, see [the examples folder](https://git.asonix.d
|
||||||
|
|
||||||
#### Bringing your own server/worker implementation
|
#### Bringing your own server/worker implementation
|
||||||
If you want to create your own jobs processor based on this idea, you can depend on the
|
If you want to create your own jobs processor based on this idea, you can depend on the
|
||||||
`background-jobs-core` crate, which provides the Processor and Job traits, as well as some
|
`background-jobs-core` crate, which provides the Job trait, 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.
|
||||||
|
|
||||||
### Contributing
|
### Contributing
|
||||||
|
|
|
@ -11,7 +11,7 @@ actix = "0.10.0-alpha.2"
|
||||||
actix-rt = "1.0.0"
|
actix-rt = "1.0.0"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
async-trait = "0.1.24"
|
async-trait = "0.1.24"
|
||||||
background-jobs = { version = "0.8.0-alpha.0", path = "../.." }
|
background-jobs = { version = "0.8.0-alpha.1", path = "../.." }
|
||||||
env_logger = "0.7"
|
env_logger = "0.7"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
sled-extensions = { version = "0.3.0-alpha.0", git = "https://git.asonix.dog/Aardwolf/sled-extensions" }
|
sled-extensions = { version = "0.3.0-alpha.0", git = "https://git.asonix.dog/Aardwolf/sled-extensions" }
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use background_jobs::{create_server, Job, MaxRetries, Processor, WorkerConfig};
|
use background_jobs::{create_server, Job, MaxRetries, WorkerConfig};
|
||||||
use futures::future::{ok, Ready};
|
use futures::future::{ok, Ready};
|
||||||
|
|
||||||
const DEFAULT_QUEUE: &'static str = "default";
|
const DEFAULT_QUEUE: &'static str = "default";
|
||||||
|
@ -15,9 +15,6 @@ pub struct MyJob {
|
||||||
other_usize: usize,
|
other_usize: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct MyProcessor;
|
|
||||||
|
|
||||||
#[actix_rt::main]
|
#[actix_rt::main]
|
||||||
async fn main() -> Result<(), Error> {
|
async fn main() -> Result<(), Error> {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
@ -39,8 +36,8 @@ async fn main() -> Result<(), Error> {
|
||||||
|
|
||||||
// Configure and start our workers
|
// Configure and start our workers
|
||||||
WorkerConfig::new(move || MyState::new("My App"))
|
WorkerConfig::new(move || MyState::new("My App"))
|
||||||
.register(MyProcessor)
|
.register::<MyJob>()
|
||||||
.set_processor_count(DEFAULT_QUEUE, 16)
|
.set_worker_count(DEFAULT_QUEUE, 16)
|
||||||
.start(queue_handle.clone());
|
.start(queue_handle.clone());
|
||||||
|
|
||||||
// Queue our jobs
|
// Queue our jobs
|
||||||
|
@ -72,25 +69,13 @@ impl MyJob {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Job for MyJob {
|
impl Job for MyJob {
|
||||||
type Processor = MyProcessor;
|
|
||||||
type State = MyState;
|
type State = MyState;
|
||||||
type Future = Ready<Result<(), Error>>;
|
type Future = Ready<Result<(), Error>>;
|
||||||
|
|
||||||
fn run(self, state: MyState) -> Self::Future {
|
// The name of the job. It is super important that each job has a unique name,
|
||||||
println!("{}: args, {:?}", state.app_name, self);
|
// because otherwise one job will overwrite another job when they're being
|
||||||
|
|
||||||
ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Processor for MyProcessor {
|
|
||||||
// The kind of job this processor should execute
|
|
||||||
type Job = MyJob;
|
|
||||||
|
|
||||||
// The name of the processor. It is super important that each processor has a unique name,
|
|
||||||
// because otherwise one processor will overwrite another processor when they're being
|
|
||||||
// registered.
|
// registered.
|
||||||
const NAME: &'static str = "MyProcessor";
|
const NAME: &'static str = "MyJob";
|
||||||
|
|
||||||
// The queue that this processor belongs to
|
// The queue that this processor belongs to
|
||||||
//
|
//
|
||||||
|
@ -104,4 +89,10 @@ impl Processor for MyProcessor {
|
||||||
//
|
//
|
||||||
// Jobs can optionally override this value
|
// Jobs can optionally override this value
|
||||||
const MAX_RETRIES: MaxRetries = MaxRetries::Count(1);
|
const MAX_RETRIES: MaxRetries = MaxRetries::Count(1);
|
||||||
|
|
||||||
|
fn run(self, state: MyState) -> Self::Future {
|
||||||
|
println!("{}: args, {:?}", state.app_name, self);
|
||||||
|
|
||||||
|
ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "background-jobs-actix"
|
name = "background-jobs-actix"
|
||||||
description = "in-process jobs processor based on Actix"
|
description = "in-process jobs processor based on Actix"
|
||||||
version = "0.7.0-alpha.0"
|
version = "0.8.0-alpha.0"
|
||||||
license-file = "../LICENSE"
|
license-file = "../LICENSE"
|
||||||
authors = ["asonix <asonix@asonix.dog>"]
|
authors = ["asonix <asonix@asonix.dog>"]
|
||||||
repository = "https://git.asonix.dog/Aardwolf/background-jobs"
|
repository = "https://git.asonix.dog/Aardwolf/background-jobs"
|
||||||
|
@ -14,7 +14,7 @@ actix = "0.10.0-alpha.2"
|
||||||
actix-rt = "1.0.0"
|
actix-rt = "1.0.0"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
async-trait = "0.1.24"
|
async-trait = "0.1.24"
|
||||||
background-jobs-core = { version = "0.7", path = "../jobs-core", features = ["with-actix"] }
|
background-jobs-core = { version = "0.8.0-alpha.0", path = "../jobs-core", features = ["with-actix"] }
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
num_cpus = "1.10.0"
|
num_cpus = "1.10.0"
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
//! ```rust,ignore
|
//! ```rust,ignore
|
||||||
//! use actix::System;
|
//! use actix::System;
|
||||||
//! use anyhow::Error;
|
//! use anyhow::Error;
|
||||||
//! use background_jobs::{create_server, Backoff, Job, MaxRetries, Processor, WorkerConfig};
|
//! use background_jobs::{create_server, Backoff, Job, MaxRetries, WorkerConfig};
|
||||||
//! use futures::future::{ok, Ready};
|
//! use futures::future::{ok, Ready};
|
||||||
//!
|
//!
|
||||||
//! const DEFAULT_QUEUE: &'static str = "default";
|
//! const DEFAULT_QUEUE: &'static str = "default";
|
||||||
|
@ -30,9 +30,6 @@
|
||||||
//! other_usize: usize,
|
//! other_usize: usize,
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! #[derive(Clone, Debug)]
|
|
||||||
//! pub struct MyProcessor;
|
|
||||||
//!
|
|
||||||
//! #[actix_rt::main]
|
//! #[actix_rt::main]
|
||||||
//! async fn main() -> Result<(), Error> {
|
//! async fn main() -> Result<(), Error> {
|
||||||
//! // Set up our Storage
|
//! // Set up our Storage
|
||||||
|
@ -45,8 +42,8 @@
|
||||||
//!
|
//!
|
||||||
//! // Configure and start our workers
|
//! // Configure and start our workers
|
||||||
//! WorkerConfig::new(move || MyState::new("My App"))
|
//! WorkerConfig::new(move || MyState::new("My App"))
|
||||||
//! .register(MyProcessor)
|
//! .register::<MyJob>()
|
||||||
//! .set_processor_count(DEFAULT_QUEUE, 16)
|
//! .set_worker_count(DEFAULT_QUEUE, 16)
|
||||||
//! .start(queue_handle.clone());
|
//! .start(queue_handle.clone());
|
||||||
//!
|
//!
|
||||||
//! // Queue our jobs
|
//! // Queue our jobs
|
||||||
|
@ -78,25 +75,13 @@
|
||||||
//!
|
//!
|
||||||
//! #[async_trait::async_trait]
|
//! #[async_trait::async_trait]
|
||||||
//! impl Job for MyJob {
|
//! impl Job for MyJob {
|
||||||
//! type Processor = MyProcessor;
|
|
||||||
//! type State = MyState;
|
//! type State = MyState;
|
||||||
//! type Future = Ready<Result<(), Error>>;
|
//! type Future = Ready<Result<(), Error>>;
|
||||||
//!
|
//!
|
||||||
//! async fn run(self, state: MyState) -> Self::Future {
|
//! // The name of the job. It is super important that each job has a unique name,
|
||||||
//! println!("{}: args, {:?}", state.app_name, self);
|
//! // because otherwise one job will overwrite another job when they're being
|
||||||
//!
|
|
||||||
//! ok(())
|
|
||||||
//! }
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! impl Processor for MyProcessor {
|
|
||||||
//! // The kind of job this processor should execute
|
|
||||||
//! type Job = MyJob;
|
|
||||||
//!
|
|
||||||
//! // The name of the processor. It is super important that each processor has a unique name,
|
|
||||||
//! // because otherwise one processor will overwrite another processor when they're being
|
|
||||||
//! // registered.
|
//! // registered.
|
||||||
//! const NAME: &'static str = "MyProcessor";
|
//! const NAME: &'static str = "MyJob";
|
||||||
//!
|
//!
|
||||||
//! // The queue that this processor belongs to
|
//! // The queue that this processor belongs to
|
||||||
//! //
|
//! //
|
||||||
|
@ -123,12 +108,18 @@
|
||||||
//! // The timeout defines when a job is allowed to be considered dead, and so can be retried
|
//! // The timeout defines when a job is allowed to be considered dead, and so can be retried
|
||||||
//! // by the job processor. The value is in milliseconds and defaults to 15,000
|
//! // by the job processor. The value is in milliseconds and defaults to 15,000
|
||||||
//! const TIMEOUT: i64 = 15_000
|
//! const TIMEOUT: i64 = 15_000
|
||||||
|
//!
|
||||||
|
//! async fn run(self, state: MyState) -> Self::Future {
|
||||||
|
//! println!("{}: args, {:?}", state.app_name, self);
|
||||||
|
//!
|
||||||
|
//! ok(())
|
||||||
|
//! }
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use actix::Arbiter;
|
use actix::Arbiter;
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use background_jobs_core::{Job, Processor, ProcessorMap, Stats, Storage};
|
use background_jobs_core::{new_job, Job, ProcessorMap, Stats, Storage};
|
||||||
use log::error;
|
use log::error;
|
||||||
use std::{collections::BTreeMap, sync::Arc, time::Duration};
|
use std::{collections::BTreeMap, sync::Arc, time::Duration};
|
||||||
|
|
||||||
|
@ -160,7 +151,7 @@ where
|
||||||
/// Worker Configuration
|
/// Worker Configuration
|
||||||
///
|
///
|
||||||
/// This type is used for configuring and creating workers to process jobs. Before starting the
|
/// This type is used for configuring and creating workers to process jobs. Before starting the
|
||||||
/// workers, register `Processor` types with this struct. This worker registration allows for
|
/// workers, register `Job` types with this struct. This worker registration allows for
|
||||||
/// different worker processes to handle different sets of workers.
|
/// different worker processes to handle different sets of workers.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct WorkerConfig<State>
|
pub struct WorkerConfig<State>
|
||||||
|
@ -187,18 +178,17 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register a `Processor` with the worker
|
/// Register a `Job` with the worker
|
||||||
///
|
///
|
||||||
/// This enables the worker to handle jobs associated with this processor. If a processor is
|
/// This enables the worker to handle jobs associated with this processor. If a processor is
|
||||||
/// not registered, none of it's jobs will be run, even if another processor handling the same
|
/// not registered, none of it's jobs will be run, even if another processor handling the same
|
||||||
/// job queue is registered.
|
/// job queue is registered.
|
||||||
pub fn register<P, J>(mut self, processor: P) -> Self
|
pub fn register<J>(mut self) -> Self
|
||||||
where
|
where
|
||||||
P: Processor<Job = J> + Send + Sync + 'static,
|
|
||||||
J: Job<State = State>,
|
J: Job<State = State>,
|
||||||
{
|
{
|
||||||
self.queues.insert(P::QUEUE.to_owned(), 4);
|
self.queues.insert(J::QUEUE.to_owned(), 4);
|
||||||
self.processors.register_processor(processor);
|
self.processors.register::<J>();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,7 +198,7 @@ where
|
||||||
/// will handle processing all workers, regardless of how many are configured.
|
/// will handle processing all workers, regardless of how many are configured.
|
||||||
///
|
///
|
||||||
/// By default, 4 workers are spawned
|
/// By default, 4 workers are spawned
|
||||||
pub fn set_processor_count(mut self, queue: &str, count: u64) -> Self {
|
pub fn set_worker_count(mut self, queue: &str, count: u64) -> Self {
|
||||||
self.queues.insert(queue.to_owned(), count);
|
self.queues.insert(queue.to_owned(), count);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -260,7 +250,7 @@ impl QueueHandle {
|
||||||
where
|
where
|
||||||
J: Job,
|
J: Job,
|
||||||
{
|
{
|
||||||
let job = J::Processor::new_job(job)?;
|
let job = new_job(job)?;
|
||||||
let server = self.inner.clone();
|
let server = self.inner.clone();
|
||||||
actix::spawn(async move {
|
actix::spawn(async move {
|
||||||
if let Err(e) = server.new_job(job).await {
|
if let Err(e) = server.new_job(job).await {
|
||||||
|
|
|
@ -102,7 +102,7 @@ impl Server {
|
||||||
) -> Result<bool, Error> {
|
) -> Result<bool, Error> {
|
||||||
trace!("Trying to find job for worker {}", worker.id());
|
trace!("Trying to find job for worker {}", worker.id());
|
||||||
if let Ok(Some(job)) = self.storage.request_job(&queue, worker.id()).await {
|
if let Ok(Some(job)) = self.storage.request_job(&queue, worker.id()).await {
|
||||||
if let Err(job) = worker.process_job(job).await {
|
if let Err(job) = worker.process(job).await {
|
||||||
error!("Worker has hung up");
|
error!("Worker has hung up");
|
||||||
self.storage.return_job(job.unexecuted()).await?
|
self.storage.return_job(job.unexecuted()).await?
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use uuid::Uuid;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait Worker {
|
pub trait Worker {
|
||||||
async fn process_job(&self, job: JobInfo) -> Result<(), JobInfo>;
|
async fn process(&self, job: JobInfo) -> Result<(), JobInfo>;
|
||||||
|
|
||||||
fn id(&self) -> Uuid;
|
fn id(&self) -> Uuid;
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ pub(crate) struct LocalWorkerHandle {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Worker for LocalWorkerHandle {
|
impl Worker for LocalWorkerHandle {
|
||||||
async fn process_job(&self, job: JobInfo) -> Result<(), JobInfo> {
|
async fn process(&self, job: JobInfo) -> Result<(), JobInfo> {
|
||||||
match self.tx.clone().send(job).await {
|
match self.tx.clone().send(job).await {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Unable to send job");
|
error!("Unable to send job");
|
||||||
|
@ -65,7 +65,7 @@ pub(crate) fn local_worker<State>(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
while let Some(job) = rx.recv().await {
|
while let Some(job) = rx.recv().await {
|
||||||
let return_job = processors.process_job(job).await;
|
let return_job = processors.process(job).await;
|
||||||
|
|
||||||
if let Err(e) = server.return_job(return_job).await {
|
if let Err(e) = server.return_job(return_job).await {
|
||||||
error!("Error returning job, {}", e);
|
error!("Error returning job, {}", e);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "background-jobs-core"
|
name = "background-jobs-core"
|
||||||
description = "Core types for implementing an asynchronous jobs processor"
|
description = "Core types for implementing an asynchronous jobs processor"
|
||||||
version = "0.7.0"
|
version = "0.8.0-alpha.0"
|
||||||
license-file = "../LICENSE"
|
license-file = "../LICENSE"
|
||||||
authors = ["asonix <asonix@asonix.dog>"]
|
authors = ["asonix <asonix@asonix.dog>"]
|
||||||
repository = "https://git.asonix.dog/Aardwolf/background-jobs"
|
repository = "https://git.asonix.dog/Aardwolf/background-jobs"
|
||||||
|
@ -14,7 +14,7 @@ default = []
|
||||||
with-actix = ["actix", "tokio"]
|
with-actix = ["actix", "tokio"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix = { version = "0.10.0-alpha.1", optional = true }
|
actix = { version = "0.10.0-alpha.2", optional = true }
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
async-trait = "0.1.24"
|
async-trait = "0.1.24"
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{Backoff, Job, MaxRetries, Processor};
|
use crate::{Backoff, Job, MaxRetries};
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use log::error;
|
use log::error;
|
||||||
use serde::{de::DeserializeOwned, ser::Serialize};
|
use serde::{de::DeserializeOwned, ser::Serialize};
|
||||||
|
@ -10,16 +10,47 @@ use tokio::sync::oneshot;
|
||||||
/// This trait is specific to Actix, and will automatically implement the Job trait with the
|
/// This trait is specific to Actix, and will automatically implement the Job trait with the
|
||||||
/// proper translation from ?Send futures to Send futures
|
/// proper translation from ?Send futures to Send futures
|
||||||
pub trait ActixJob: Serialize + DeserializeOwned + 'static {
|
pub trait ActixJob: Serialize + DeserializeOwned + 'static {
|
||||||
/// The processor this job is associated with. The job's processor can be used to create a
|
|
||||||
/// 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.
|
/// The application state provided to this job at runtime.
|
||||||
type State: Clone + 'static;
|
type State: Clone + 'static;
|
||||||
|
|
||||||
/// The future returned by this job
|
/// The future returned by this job
|
||||||
|
///
|
||||||
|
/// Importantly, this Future does not require Send
|
||||||
type Future: Future<Output = Result<(), Error>>;
|
type Future: Future<Output = Result<(), Error>>;
|
||||||
|
|
||||||
|
/// The name of the job
|
||||||
|
///
|
||||||
|
/// This name must be unique!!!
|
||||||
|
const NAME: &'static str;
|
||||||
|
|
||||||
|
/// The name of the default queue for this job
|
||||||
|
///
|
||||||
|
/// This can be overridden on an individual-job level, but if a non-existant queue is supplied,
|
||||||
|
/// the job will never be processed.
|
||||||
|
const QUEUE: &'static str = "default";
|
||||||
|
|
||||||
|
/// Define the default number of retries for this job
|
||||||
|
///
|
||||||
|
/// Defaults to Count(5)
|
||||||
|
/// Jobs can override
|
||||||
|
const MAX_RETRIES: MaxRetries = MaxRetries::Count(5);
|
||||||
|
|
||||||
|
/// Define the default backoff strategy for this job
|
||||||
|
///
|
||||||
|
/// Defaults to Exponential(2)
|
||||||
|
/// Jobs can override
|
||||||
|
const BACKOFF: Backoff = Backoff::Exponential(2);
|
||||||
|
|
||||||
|
/// Define the maximum number of milliseconds a job should be allowed to run before being
|
||||||
|
/// considered dead.
|
||||||
|
///
|
||||||
|
/// This is important for allowing the job server to reap processes that were started but never
|
||||||
|
/// completed.
|
||||||
|
///
|
||||||
|
/// Defaults to 15 seconds
|
||||||
|
/// Jobs can override
|
||||||
|
const TIMEOUT: i64 = 15_000;
|
||||||
|
|
||||||
/// 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
|
||||||
|
@ -31,26 +62,22 @@ pub trait ActixJob: Serialize + DeserializeOwned + 'static {
|
||||||
/// an actor in an actix-based system.
|
/// an actor in an actix-based system.
|
||||||
fn run(self, state: Self::State) -> Self::Future;
|
fn run(self, state: Self::State) -> Self::Future;
|
||||||
|
|
||||||
/// If this job should not use the default queue for its processor, this can be overridden in
|
/// If this job should not use it's default queue, this can be overridden in
|
||||||
/// user-code.
|
/// user-code.
|
||||||
///
|
fn queue(&self) -> &str {
|
||||||
/// Jobs will only be processed by processors that are registered, and if a queue is supplied
|
Self::QUEUE
|
||||||
/// here that is not associated with a valid processor for this job, it will never be
|
|
||||||
/// processed.
|
|
||||||
fn queue(&self) -> Option<&str> {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If this job should not use the default maximum retry count for its processor, this can be
|
/// If this job should not use it's default maximum retry count, this can be
|
||||||
/// overridden in user-code.
|
/// overridden in user-code.
|
||||||
fn max_retries(&self) -> Option<MaxRetries> {
|
fn max_retries(&self) -> MaxRetries {
|
||||||
None
|
Self::MAX_RETRIES
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If this job should not use the default backoff strategy for its processor, this can be
|
/// If this job should not use it's default backoff strategy, this can be
|
||||||
/// overridden in user-code.
|
/// overridden in user-code.
|
||||||
fn backoff_strategy(&self) -> Option<Backoff> {
|
fn backoff_strategy(&self) -> Backoff {
|
||||||
None
|
Self::BACKOFF
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Define the maximum number of milliseconds this job should be allowed to run before being
|
/// Define the maximum number of milliseconds this job should be allowed to run before being
|
||||||
|
@ -58,8 +85,8 @@ pub trait ActixJob: Serialize + DeserializeOwned + 'static {
|
||||||
///
|
///
|
||||||
/// This is important for allowing the job server to reap processes that were started but never
|
/// This is important for allowing the job server to reap processes that were started but never
|
||||||
/// completed.
|
/// completed.
|
||||||
fn timeout(&self) -> Option<i64> {
|
fn timeout(&self) -> i64 {
|
||||||
None
|
Self::TIMEOUT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,10 +94,11 @@ impl<T> Job for T
|
||||||
where
|
where
|
||||||
T: ActixJob,
|
T: ActixJob,
|
||||||
{
|
{
|
||||||
type Processor = T::Processor;
|
|
||||||
type State = T::State;
|
type State = T::State;
|
||||||
type Future = Pin<Box<dyn Future<Output = Result<(), Error>> + Send>>;
|
type Future = Pin<Box<dyn Future<Output = Result<(), Error>> + Send>>;
|
||||||
|
|
||||||
|
const NAME: &'static str = <Self as ActixJob>::NAME;
|
||||||
|
|
||||||
fn run(self, state: Self::State) -> Self::Future {
|
fn run(self, state: Self::State) -> Self::Future {
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
|
|
||||||
|
@ -83,19 +111,19 @@ where
|
||||||
Box::pin(async move { rx.await? })
|
Box::pin(async move { rx.await? })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn queue(&self) -> Option<&str> {
|
fn queue(&self) -> &str {
|
||||||
ActixJob::queue(self)
|
ActixJob::queue(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn max_retries(&self) -> Option<MaxRetries> {
|
fn max_retries(&self) -> MaxRetries {
|
||||||
ActixJob::max_retries(self)
|
ActixJob::max_retries(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn backoff_strategy(&self) -> Option<Backoff> {
|
fn backoff_strategy(&self) -> Backoff {
|
||||||
ActixJob::backoff_strategy(self)
|
ActixJob::backoff_strategy(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn timeout(&self) -> Option<i64> {
|
fn timeout(&self) -> i64 {
|
||||||
ActixJob::timeout(self)
|
ActixJob::timeout(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,89 @@
|
||||||
use crate::{Backoff, MaxRetries, Processor};
|
use crate::{Backoff, JobError, MaxRetries, NewJobInfo};
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
|
use chrono::{offset::Utc, DateTime};
|
||||||
use serde::{de::DeserializeOwned, ser::Serialize};
|
use serde::{de::DeserializeOwned, ser::Serialize};
|
||||||
use std::future::Future;
|
use serde_json::Value;
|
||||||
|
use std::{future::Future, pin::Pin};
|
||||||
|
|
||||||
/// The Job trait defines parameters pertaining to an instance of background job
|
/// The Job trait defines parameters pertaining to an instance of background job
|
||||||
|
///
|
||||||
|
/// Jobs are defnitions of work to be executed.
|
||||||
|
///
|
||||||
|
/// The simplest implementation defines the job's State and Future types, NAME contant, and
|
||||||
|
/// run method.
|
||||||
|
///
|
||||||
|
/// ### Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use anyhow::Error;
|
||||||
|
/// use background_jobs_core::{Job, new_job};
|
||||||
|
/// use futures::future::{ok, Ready};
|
||||||
|
/// use log::info;
|
||||||
|
///
|
||||||
|
/// #[derive(serde::Deserialize, serde::Serialize)]
|
||||||
|
/// struct MyJob {
|
||||||
|
/// count: i64,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl Job for MyJob {
|
||||||
|
/// type State = ();
|
||||||
|
/// type Future = Ready<Result<(), Error>>;
|
||||||
|
///
|
||||||
|
/// const NAME: &'static str = "MyJob";
|
||||||
|
///
|
||||||
|
/// fn run(self, _: Self::State) -> Self::Future {
|
||||||
|
/// info!("Processing {}", self.count);
|
||||||
|
///
|
||||||
|
/// ok(())
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn main() -> Result<(), Error> {
|
||||||
|
/// let job = new_job(MyJob { count: 1234 })?;
|
||||||
|
///
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
pub trait Job: Serialize + DeserializeOwned + 'static {
|
pub trait Job: Serialize + DeserializeOwned + 'static {
|
||||||
/// The processor this job is associated with. The job's processor can be used to create a
|
|
||||||
/// 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.
|
/// The application state provided to this job at runtime.
|
||||||
type State: Clone + 'static;
|
type State: Clone + 'static;
|
||||||
|
|
||||||
/// The future returned by this job
|
/// The future returned by this job
|
||||||
type Future: Future<Output = Result<(), Error>> + Send;
|
type Future: Future<Output = Result<(), Error>> + Send;
|
||||||
|
|
||||||
|
/// The name of the job
|
||||||
|
///
|
||||||
|
/// This name must be unique!!!
|
||||||
|
const NAME: &'static str;
|
||||||
|
|
||||||
|
/// The name of the default queue for this job
|
||||||
|
///
|
||||||
|
/// This can be overridden on an individual-job level, but if a non-existant queue is supplied,
|
||||||
|
/// the job will never be processed.
|
||||||
|
const QUEUE: &'static str = "default";
|
||||||
|
|
||||||
|
/// Define the default number of retries for this job
|
||||||
|
///
|
||||||
|
/// Defaults to Count(5)
|
||||||
|
/// Jobs can override
|
||||||
|
const MAX_RETRIES: MaxRetries = MaxRetries::Count(5);
|
||||||
|
|
||||||
|
/// Define the default backoff strategy for this job
|
||||||
|
///
|
||||||
|
/// Defaults to Exponential(2)
|
||||||
|
/// Jobs can override
|
||||||
|
const BACKOFF: Backoff = Backoff::Exponential(2);
|
||||||
|
|
||||||
|
/// Define the maximum number of milliseconds a job should be allowed to run before being
|
||||||
|
/// considered dead.
|
||||||
|
///
|
||||||
|
/// This is important for allowing the job server to reap processes that were started but never
|
||||||
|
/// completed.
|
||||||
|
///
|
||||||
|
/// Defaults to 15 seconds
|
||||||
|
/// Jobs can override
|
||||||
|
const TIMEOUT: i64 = 15_000;
|
||||||
|
|
||||||
/// 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
|
||||||
|
@ -26,26 +95,22 @@ pub trait Job: Serialize + DeserializeOwned + 'static {
|
||||||
/// an actor in an actix-based system.
|
/// an actor in an actix-based system.
|
||||||
fn run(self, state: Self::State) -> Self::Future;
|
fn run(self, state: Self::State) -> Self::Future;
|
||||||
|
|
||||||
/// If this job should not use the default queue for its processor, this can be overridden in
|
/// If this job should not use it's default queue, this can be overridden in
|
||||||
/// user-code.
|
/// user-code.
|
||||||
///
|
fn queue(&self) -> &str {
|
||||||
/// Jobs will only be processed by processors that are registered, and if a queue is supplied
|
Self::QUEUE
|
||||||
/// here that is not associated with a valid processor for this job, it will never be
|
|
||||||
/// processed.
|
|
||||||
fn queue(&self) -> Option<&str> {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If this job should not use the default maximum retry count for its processor, this can be
|
/// If this job should not use it's default maximum retry count, this can be
|
||||||
/// overridden in user-code.
|
/// overridden in user-code.
|
||||||
fn max_retries(&self) -> Option<MaxRetries> {
|
fn max_retries(&self) -> MaxRetries {
|
||||||
None
|
Self::MAX_RETRIES
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If this job should not use the default backoff strategy for its processor, this can be
|
/// If this job should not use it's default backoff strategy, this can be
|
||||||
/// overridden in user-code.
|
/// overridden in user-code.
|
||||||
fn backoff_strategy(&self) -> Option<Backoff> {
|
fn backoff_strategy(&self) -> Backoff {
|
||||||
None
|
Self::BACKOFF
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Define the maximum number of milliseconds this job should be allowed to run before being
|
/// Define the maximum number of milliseconds this job should be allowed to run before being
|
||||||
|
@ -53,7 +118,56 @@ pub trait Job: Serialize + DeserializeOwned + 'static {
|
||||||
///
|
///
|
||||||
/// This is important for allowing the job server to reap processes that were started but never
|
/// This is important for allowing the job server to reap processes that were started but never
|
||||||
/// completed.
|
/// completed.
|
||||||
fn timeout(&self) -> Option<i64> {
|
fn timeout(&self) -> i64 {
|
||||||
None
|
Self::TIMEOUT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A provided method to create a new JobInfo from provided arguments
|
||||||
|
pub fn new_job<J>(job: J) -> Result<NewJobInfo, Error>
|
||||||
|
where
|
||||||
|
J: Job,
|
||||||
|
{
|
||||||
|
let job = NewJobInfo::new(
|
||||||
|
J::NAME.to_owned(),
|
||||||
|
job.queue().to_owned(),
|
||||||
|
job.max_retries(),
|
||||||
|
job.backoff_strategy(),
|
||||||
|
job.timeout(),
|
||||||
|
serde_json::to_value(job).map_err(|_| ToJson)?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(job)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a NewJobInfo to schedule a job to be performed after a certain time
|
||||||
|
pub fn new_scheduled_job<J>(job: J, after: DateTime<Utc>) -> Result<NewJobInfo, Error>
|
||||||
|
where
|
||||||
|
J: Job,
|
||||||
|
{
|
||||||
|
let mut job = new_job(job)?;
|
||||||
|
job.schedule(after);
|
||||||
|
|
||||||
|
Ok(job)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A provided method to coerce arguments into the expected type and run the job
|
||||||
|
pub fn process<J>(
|
||||||
|
args: Value,
|
||||||
|
state: J::State,
|
||||||
|
) -> Pin<Box<dyn Future<Output = Result<(), JobError>> + Send>>
|
||||||
|
where
|
||||||
|
J: Job,
|
||||||
|
{
|
||||||
|
let res = serde_json::from_value::<J>(args).map(move |job| job.run(state));
|
||||||
|
|
||||||
|
Box::pin(async move {
|
||||||
|
res?.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, thiserror::Error)]
|
||||||
|
#[error("Failed to to turn job into value")]
|
||||||
|
pub struct ToJson;
|
||||||
|
|
|
@ -26,10 +26,10 @@ impl ReturnJobInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn missing_processor(id: Uuid) -> Self {
|
pub(crate) fn unregistered(id: Uuid) -> Self {
|
||||||
ReturnJobInfo {
|
ReturnJobInfo {
|
||||||
id,
|
id,
|
||||||
result: JobResult::MissingProcessor,
|
result: JobResult::Unregistered,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,8 +37,8 @@ impl ReturnJobInfo {
|
||||||
#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
/// Information about a newly created job
|
/// Information about a newly created job
|
||||||
pub struct NewJobInfo {
|
pub struct NewJobInfo {
|
||||||
/// Name of the processor that should handle this job
|
/// Name of the job
|
||||||
processor: String,
|
name: String,
|
||||||
|
|
||||||
/// Name of the queue that this job is a part of
|
/// Name of the queue that this job is a part of
|
||||||
queue: String,
|
queue: String,
|
||||||
|
@ -67,15 +67,15 @@ impl NewJobInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
processor: String,
|
name: String,
|
||||||
queue: String,
|
queue: String,
|
||||||
args: Value,
|
|
||||||
max_retries: MaxRetries,
|
max_retries: MaxRetries,
|
||||||
backoff_strategy: Backoff,
|
backoff_strategy: Backoff,
|
||||||
timeout: i64,
|
timeout: i64,
|
||||||
|
args: Value,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
NewJobInfo {
|
NewJobInfo {
|
||||||
processor,
|
name,
|
||||||
queue,
|
queue,
|
||||||
args,
|
args,
|
||||||
max_retries,
|
max_retries,
|
||||||
|
@ -98,7 +98,7 @@ impl NewJobInfo {
|
||||||
pub(crate) fn with_id(self, id: Uuid) -> JobInfo {
|
pub(crate) fn with_id(self, id: Uuid) -> JobInfo {
|
||||||
JobInfo {
|
JobInfo {
|
||||||
id,
|
id,
|
||||||
processor: self.processor,
|
name: self.name,
|
||||||
queue: self.queue,
|
queue: self.queue,
|
||||||
status: JobStatus::Pending,
|
status: JobStatus::Pending,
|
||||||
args: self.args,
|
args: self.args,
|
||||||
|
@ -116,15 +116,13 @@ impl NewJobInfo {
|
||||||
/// Metadata pertaining to a job that exists within the background_jobs system
|
/// Metadata pertaining to a job that exists within the background_jobs system
|
||||||
///
|
///
|
||||||
/// Although exposed publically, this type should only really be handled by the library itself, and
|
/// Although exposed publically, this type should only really be handled by the library itself, and
|
||||||
/// is impossible to create outside of a
|
/// is impossible to create outside of the new_job method.
|
||||||
/// [Processor](https://docs.rs/background-jobs/0.4.0/background_jobs/trait.Processor.html)'s
|
|
||||||
/// new_job method.
|
|
||||||
pub struct JobInfo {
|
pub struct JobInfo {
|
||||||
/// ID of the job
|
/// ID of the job
|
||||||
id: Uuid,
|
id: Uuid,
|
||||||
|
|
||||||
/// Name of the processor that should handle this job
|
/// Name of the job
|
||||||
processor: String,
|
name: String,
|
||||||
|
|
||||||
/// Name of the queue that this job is a part of
|
/// Name of the queue that this job is a part of
|
||||||
queue: String,
|
queue: String,
|
||||||
|
@ -166,8 +164,8 @@ impl JobInfo {
|
||||||
self.updated_at = Utc::now();
|
self.updated_at = Utc::now();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn processor(&self) -> &str {
|
pub(crate) fn name(&self) -> &str {
|
||||||
&self.processor
|
&self.name
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn args(&self) -> Value {
|
pub(crate) fn args(&self) -> Value {
|
||||||
|
|
|
@ -12,15 +12,13 @@ use anyhow::Error;
|
||||||
mod actix_job;
|
mod actix_job;
|
||||||
mod job;
|
mod job;
|
||||||
mod job_info;
|
mod job_info;
|
||||||
mod processor;
|
|
||||||
mod processor_map;
|
mod processor_map;
|
||||||
mod stats;
|
mod stats;
|
||||||
mod storage;
|
mod storage;
|
||||||
|
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
job::Job,
|
job::{new_job, new_scheduled_job, process, Job},
|
||||||
job_info::{JobInfo, NewJobInfo, ReturnJobInfo},
|
job_info::{JobInfo, NewJobInfo, ReturnJobInfo},
|
||||||
processor::Processor,
|
|
||||||
processor_map::{CachedProcessorMap, ProcessorMap},
|
processor_map::{CachedProcessorMap, ProcessorMap},
|
||||||
stats::{JobStat, Stats},
|
stats::{JobStat, Stats},
|
||||||
storage::{memory_storage, Storage},
|
storage::{memory_storage, Storage},
|
||||||
|
@ -30,7 +28,7 @@ pub use crate::{
|
||||||
pub use actix_job::ActixJob;
|
pub use actix_job::ActixJob;
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
/// The error type returned by a `Processor`'s `process` method
|
/// The error type returned by the `process` method
|
||||||
pub enum JobError {
|
pub enum JobError {
|
||||||
/// Some error occurred while processing the job
|
/// Some error occurred while processing the job
|
||||||
#[error("Error performing job: {0}")]
|
#[error("Error performing job: {0}")]
|
||||||
|
@ -40,9 +38,9 @@ pub enum JobError {
|
||||||
#[error("Could not make JSON value from arguments")]
|
#[error("Could not make JSON value from arguments")]
|
||||||
Json,
|
Json,
|
||||||
|
|
||||||
/// No processor was present to handle a given job
|
/// This job type was not registered for this client
|
||||||
#[error("No processor available for job")]
|
#[error("This job type was not registered for the client")]
|
||||||
MissingProcessor,
|
Unregistered,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
|
@ -54,8 +52,8 @@ pub enum JobResult {
|
||||||
/// The job failed
|
/// The job failed
|
||||||
Failure,
|
Failure,
|
||||||
|
|
||||||
/// There was no processor to run the job
|
/// The worker had no concept of this job
|
||||||
MissingProcessor,
|
Unregistered,
|
||||||
|
|
||||||
/// The worker requesting this job closed
|
/// The worker requesting this job closed
|
||||||
Unexecuted,
|
Unexecuted,
|
||||||
|
@ -72,9 +70,9 @@ impl JobResult {
|
||||||
JobResult::Failure
|
JobResult::Failure
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Indicate that the job's processor is not present
|
/// Indicate that the job was not registered for this worker
|
||||||
pub fn missing_processor() -> Self {
|
pub fn unregistered() -> Self {
|
||||||
JobResult::MissingProcessor
|
JobResult::Unregistered
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the job failed
|
/// Check if the job failed
|
||||||
|
@ -88,8 +86,8 @@ impl JobResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the job is missing it's processor
|
/// Check if the job is missing it's processor
|
||||||
pub fn is_missing_processor(&self) -> bool {
|
pub fn is_unregistered(&self) -> bool {
|
||||||
*self == JobResult::MissingProcessor
|
*self == JobResult::Unregistered
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the job was returned without an execution attempt
|
/// Check if the job was returned without an execution attempt
|
||||||
|
|
|
@ -1,180 +0,0 @@
|
||||||
use crate::{Backoff, Job, JobError, MaxRetries, NewJobInfo};
|
|
||||||
use anyhow::Error;
|
|
||||||
use chrono::{offset::Utc, DateTime};
|
|
||||||
use serde_json::Value;
|
|
||||||
use std::{future::Future, pin::Pin};
|
|
||||||
|
|
||||||
/// ## The Processor trait
|
|
||||||
///
|
|
||||||
/// Processors define the logic spawning jobs such as
|
|
||||||
/// - The job's name
|
|
||||||
/// - The job's default queue
|
|
||||||
/// - The job's default maximum number of retries
|
|
||||||
/// - The job's [backoff
|
|
||||||
/// strategy](https://docs.rs/background-jobs/0.4.0/background_jobs/enum.Backoff.html)
|
|
||||||
///
|
|
||||||
/// Processors also provide the default mechanism for running a job, and the only mechanism for
|
|
||||||
/// creating a
|
|
||||||
/// [JobInfo](https://docs.rs/background-jobs-core/0.4.0/background_jobs_core/struct.JobInfo.html),
|
|
||||||
/// which is the type required for queuing jobs to be executed.
|
|
||||||
///
|
|
||||||
/// ### Example
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use anyhow::Error;
|
|
||||||
/// use background_jobs_core::{Job, Processor};
|
|
||||||
/// use futures::future::{ok, Ready};
|
|
||||||
/// use log::info;
|
|
||||||
///
|
|
||||||
/// #[derive(serde::Deserialize, serde::Serialize)]
|
|
||||||
/// struct MyJob {
|
|
||||||
/// count: i32,
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// impl Job for MyJob {
|
|
||||||
/// type Processor = MyProcessor;
|
|
||||||
/// type State = ();
|
|
||||||
/// type Future = Ready<Result<(), Error>>;
|
|
||||||
///
|
|
||||||
/// fn run(self, _state: Self::State) -> Self::Future {
|
|
||||||
/// info!("Processing {}", self.count);
|
|
||||||
///
|
|
||||||
/// ok(())
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// #[derive(Clone)]
|
|
||||||
/// struct MyProcessor;
|
|
||||||
///
|
|
||||||
/// impl Processor for MyProcessor {
|
|
||||||
/// type Job = MyJob;
|
|
||||||
///
|
|
||||||
/// const NAME: &'static str = "IncrementProcessor";
|
|
||||||
/// const QUEUE: &'static str = "default";
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// fn main() -> Result<(), Error> {
|
|
||||||
/// let job = MyProcessor::new_job(MyJob { count: 1234 })?;
|
|
||||||
///
|
|
||||||
/// Ok(())
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub trait Processor: Clone {
|
|
||||||
/// The job this processor will process
|
|
||||||
type Job: Job + 'static;
|
|
||||||
|
|
||||||
/// The name of the processor
|
|
||||||
///
|
|
||||||
/// This name must be unique!!! It is used to look up which processor should handle a job
|
|
||||||
const NAME: &'static str;
|
|
||||||
|
|
||||||
/// The name of the default queue for jobs created with this processor
|
|
||||||
///
|
|
||||||
/// This can be overridden on an individual-job level, but if a non-existant queue is supplied,
|
|
||||||
/// the job will never be processed.
|
|
||||||
const QUEUE: &'static str;
|
|
||||||
|
|
||||||
/// Define the default number of retries for a given processor
|
|
||||||
///
|
|
||||||
/// Defaults to Count(5)
|
|
||||||
/// Jobs can override
|
|
||||||
const MAX_RETRIES: MaxRetries = MaxRetries::Count(5);
|
|
||||||
|
|
||||||
/// Define the default backoff strategy for a given processor
|
|
||||||
///
|
|
||||||
/// Defaults to Exponential(2)
|
|
||||||
/// Jobs can override
|
|
||||||
const BACKOFF_STRATEGY: Backoff = Backoff::Exponential(2);
|
|
||||||
|
|
||||||
/// Define the maximum number of milliseconds a job should be allowed to run before being
|
|
||||||
/// considered dead.
|
|
||||||
///
|
|
||||||
/// This is important for allowing the job server to reap processes that were started but never
|
|
||||||
/// completed.
|
|
||||||
///
|
|
||||||
/// Defaults to 15 seconds
|
|
||||||
/// Jobs can override
|
|
||||||
const TIMEOUT: i64 = 15_000;
|
|
||||||
|
|
||||||
/// A provided method to create a new JobInfo from provided arguments
|
|
||||||
///
|
|
||||||
/// This is required for spawning jobs, since it enforces the relationship between the job and
|
|
||||||
/// the Processor that should handle it.
|
|
||||||
fn new_job(job: Self::Job) -> Result<NewJobInfo, Error> {
|
|
||||||
let queue = job.queue().unwrap_or(Self::QUEUE).to_owned();
|
|
||||||
let max_retries = job.max_retries().unwrap_or(Self::MAX_RETRIES);
|
|
||||||
let backoff_strategy = job.backoff_strategy().unwrap_or(Self::BACKOFF_STRATEGY);
|
|
||||||
let timeout = job.timeout().unwrap_or(Self::TIMEOUT);
|
|
||||||
|
|
||||||
let job = NewJobInfo::new(
|
|
||||||
Self::NAME.to_owned(),
|
|
||||||
queue,
|
|
||||||
serde_json::to_value(job).map_err(|_| ToJson)?,
|
|
||||||
max_retries,
|
|
||||||
backoff_strategy,
|
|
||||||
timeout,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(job)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a JobInfo to schedule a job to be performed after a certain time
|
|
||||||
fn new_scheduled_job(job: Self::Job, after: DateTime<Utc>) -> Result<NewJobInfo, Error> {
|
|
||||||
let mut job = Self::new_job(job)?;
|
|
||||||
job.schedule(after);
|
|
||||||
|
|
||||||
Ok(job)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A provided method to coerce arguments into the expected type and run the job
|
|
||||||
///
|
|
||||||
/// Advanced users may want to override this method in order to provide their own custom
|
|
||||||
/// before/after logic for certain job processors
|
|
||||||
///
|
|
||||||
/// The state passed into this method 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
|
|
||||||
/// an actor in an actix-based system.
|
|
||||||
///
|
|
||||||
/// ```rust,ignore
|
|
||||||
/// fn process(
|
|
||||||
/// &self,
|
|
||||||
/// args: Value,
|
|
||||||
/// state: S
|
|
||||||
/// ) -> Pin<Box<dyn Future<Output = Result<(), JobError>> + Send>> {
|
|
||||||
/// let res = serde_json::from_value::<Self::Job>(args);
|
|
||||||
///
|
|
||||||
/// Box::pin(async move {
|
|
||||||
/// let job = res.map_err(|_| JobError::Json)?;
|
|
||||||
/// // Perform some custom pre-job locic
|
|
||||||
///
|
|
||||||
/// job.run(state).await.map_err(JobError::Processing)?;
|
|
||||||
///
|
|
||||||
/// // Perform some custom post-job logic
|
|
||||||
/// Ok(())
|
|
||||||
/// })
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Patterns like this could be useful if you want to use the same job type for multiple
|
|
||||||
/// scenarios. Defining the `process` method for multiple `Processor`s with different
|
|
||||||
/// before/after logic for the same [`Job`] supported.
|
|
||||||
fn process(
|
|
||||||
&self,
|
|
||||||
args: Value,
|
|
||||||
state: <Self::Job as Job>::State,
|
|
||||||
) -> Pin<Box<dyn Future<Output = Result<(), JobError>> + Send>> {
|
|
||||||
// Call run on the job here because State isn't Send, but the future produced by job IS
|
|
||||||
// Send
|
|
||||||
let res = serde_json::from_value::<Self::Job>(args).map(move |job| job.run(state));
|
|
||||||
|
|
||||||
Box::pin(async move {
|
|
||||||
res?.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, thiserror::Error)]
|
|
||||||
#[error("Failed to to turn job into value")]
|
|
||||||
pub struct ToJson;
|
|
|
@ -1,21 +1,20 @@
|
||||||
use crate::{Job, JobError, JobInfo, Processor, ReturnJobInfo};
|
use crate::{Job, JobError, JobInfo, ReturnJobInfo};
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::{collections::HashMap, future::Future, pin::Pin, sync::Arc};
|
use std::{collections::HashMap, future::Future, pin::Pin, sync::Arc};
|
||||||
|
|
||||||
/// A generic function that processes a job
|
/// A generic function that processes a job
|
||||||
///
|
///
|
||||||
/// Instead of storing [`Processor`] type directly, the [`ProcessorMap`]
|
/// ProcessorMap stores these `ProcessFn` types that don't expose differences in Job types.
|
||||||
/// struct stores these `ProcessFn` types that don't expose differences in Job types.
|
|
||||||
pub type ProcessFn<S> = Arc<
|
pub type ProcessFn<S> = Arc<
|
||||||
dyn Fn(Value, S) -> Pin<Box<dyn Future<Output = Result<(), JobError>> + Send>> + Send + Sync,
|
dyn Fn(Value, S) -> Pin<Box<dyn Future<Output = Result<(), JobError>> + Send>> + Send + Sync,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
pub type StateFn<S> = Arc<dyn Fn() -> S + Send + Sync>;
|
pub type StateFn<S> = Arc<dyn Fn() -> S + Send + Sync>;
|
||||||
|
|
||||||
/// A type for storing the relationships between processor names and the processor itself
|
/// A type for storing the relationships between job names and the job itself
|
||||||
///
|
///
|
||||||
/// [`Processor`s] must be registered with the `ProcessorMap` in the initialization phase of an
|
/// [`Job`]s must be registered with the `ProcessorMap` in the initialization phase of an
|
||||||
/// application before workers are spawned in order to handle queued jobs.
|
/// application before workers are spawned in order to handle queued jobs.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ProcessorMap<S> {
|
pub struct ProcessorMap<S> {
|
||||||
|
@ -23,10 +22,10 @@ pub struct ProcessorMap<S> {
|
||||||
state_fn: StateFn<S>,
|
state_fn: StateFn<S>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A type for storing the relationships between processor names and the processor itself, with the
|
/// A type for storing the relationships between job names and the job itself, with the
|
||||||
/// state pre-cached instead of being generated from the state function each time
|
/// state pre-cached instead of being generated from the state function each time
|
||||||
///
|
///
|
||||||
/// [`Processor`s] must be registered with the `ProcessorMap` in the initialization phase of an
|
/// [`Job`]s must be registered with the `ProcessorMap` in the initialization phase of an
|
||||||
/// application before workers are spawned in order to handle queued jobs.
|
/// application before workers are spawned in order to handle queued jobs.
|
||||||
pub struct CachedProcessorMap<S> {
|
pub struct CachedProcessorMap<S> {
|
||||||
inner: HashMap<String, ProcessFn<S>>,
|
inner: HashMap<String, ProcessFn<S>>,
|
||||||
|
@ -49,18 +48,17 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register a [`Processor`] with this `ProcessorMap`.
|
/// Register a [`Job`] with this `ProcessorMap`.
|
||||||
///
|
///
|
||||||
/// `ProcessorMap`s are useless if no processors are registerd before workers are spawned, so
|
/// `ProcessorMap`s are useless if no jobs 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, J>(&mut self, processor: P)
|
pub fn register<J>(&mut self)
|
||||||
where
|
where
|
||||||
P: Processor<Job = J> + Sync + Send + 'static,
|
|
||||||
J: Job<State = S>,
|
J: Job<State = S>,
|
||||||
{
|
{
|
||||||
self.inner.insert(
|
self.inner.insert(
|
||||||
P::NAME.to_owned(),
|
J::NAME.to_owned(),
|
||||||
Arc::new(move |value, state| processor.process(value, state)),
|
Arc::new(move |value, state| crate::process::<J>(value, state)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,17 +74,17 @@ where
|
||||||
///
|
///
|
||||||
/// This should not be called from outside implementations of a backgoround-jobs runtime. It is
|
/// This should not be called from outside implementations of a backgoround-jobs runtime. It is
|
||||||
/// intended for internal use.
|
/// intended for internal use.
|
||||||
pub async fn process_job(&self, job: JobInfo) -> ReturnJobInfo {
|
pub async fn process(&self, job: JobInfo) -> ReturnJobInfo {
|
||||||
let opt = self
|
let opt = self
|
||||||
.inner
|
.inner
|
||||||
.get(job.processor())
|
.get(job.name())
|
||||||
.map(|processor| process(processor, (self.state_fn)(), job.clone()));
|
.map(|name| process(name, (self.state_fn)(), job.clone()));
|
||||||
|
|
||||||
if let Some(fut) = opt {
|
if let Some(fut) = opt {
|
||||||
fut.await
|
fut.await
|
||||||
} else {
|
} else {
|
||||||
error!("Processor {} not present", job.processor());
|
error!("Job {} not registered", job.name());
|
||||||
ReturnJobInfo::missing_processor(job.id())
|
ReturnJobInfo::unregistered(job.id())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,12 +97,12 @@ where
|
||||||
///
|
///
|
||||||
/// This should not be called from outside implementations of a backgoround-jobs runtime. It is
|
/// This should not be called from outside implementations of a backgoround-jobs runtime. It is
|
||||||
/// intended for internal use.
|
/// intended for internal use.
|
||||||
pub async fn process_job(&self, job: JobInfo) -> ReturnJobInfo {
|
pub async fn process(&self, job: JobInfo) -> ReturnJobInfo {
|
||||||
if let Some(processor) = self.inner.get(job.processor()) {
|
if let Some(name) = self.inner.get(job.name()) {
|
||||||
process(processor, self.state.clone(), job).await
|
process(name, self.state.clone(), job).await
|
||||||
} else {
|
} else {
|
||||||
error!("Processor {} not present", job.processor());
|
error!("Job {} not registered", job.name());
|
||||||
ReturnJobInfo::missing_processor(job.id())
|
ReturnJobInfo::unregistered(job.id())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,15 +110,15 @@ where
|
||||||
async fn process<S>(process_fn: &ProcessFn<S>, state: S, job: JobInfo) -> ReturnJobInfo {
|
async fn process<S>(process_fn: &ProcessFn<S>, state: S, job: JobInfo) -> ReturnJobInfo {
|
||||||
let args = job.args();
|
let args = job.args();
|
||||||
let id = job.id();
|
let id = job.id();
|
||||||
let processor = job.processor().to_owned();
|
let name = job.name().to_owned();
|
||||||
|
|
||||||
match process_fn(args, state).await {
|
match process_fn(args, state).await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
info!("Job {} completed, {}", id, processor);
|
info!("Job {} completed, {}", id, name);
|
||||||
ReturnJobInfo::pass(id)
|
ReturnJobInfo::pass(id)
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
info!("Job {} errored, {}, {}", id, processor, e);
|
info!("Job {} errored, {}, {}", id, name, e);
|
||||||
ReturnJobInfo::fail(id)
|
ReturnJobInfo::fail(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,7 +116,7 @@ pub trait Storage: Clone + Send {
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
} else if result.is_missing_processor() || result.is_unexecuted() {
|
} else if result.is_unregistered() || result.is_unexecuted() {
|
||||||
if let Some(mut job) = self.fetch_job(id).await? {
|
if let Some(mut job) = self.fetch_job(id).await? {
|
||||||
job.pending();
|
job.pending();
|
||||||
self.queue_job(job.queue(), id).await?;
|
self.queue_job(job.queue(), id).await?;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "background-jobs-sled-storage"
|
name = "background-jobs-sled-storage"
|
||||||
description = "Sled storage backend for background-jobs"
|
description = "Sled storage backend for background-jobs"
|
||||||
version = "0.4.0-alpha.0"
|
version = "0.8.0-alpha.0"
|
||||||
license-file = "../LICENSE"
|
license-file = "../LICENSE"
|
||||||
authors = ["asonix <asonix@asonix.dog>"]
|
authors = ["asonix <asonix@asonix.dog>"]
|
||||||
repository = "https://git.asonix.dog/Aardwolf/background-jobs"
|
repository = "https://git.asonix.dog/Aardwolf/background-jobs"
|
||||||
|
@ -13,7 +13,7 @@ edition = "2018"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-threadpool = "0.3.1"
|
actix-threadpool = "0.3.1"
|
||||||
async-trait = "0.1.24"
|
async-trait = "0.1.24"
|
||||||
background-jobs-core = { version = "0.7", path = "../jobs-core" }
|
background-jobs-core = { version = "0.8.0-alpha.0", path = "../jobs-core" }
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
sled-extensions = { version = "0.3.0-alpha.0", features = ["bincode", "cbor"], git = "https://git.asonix.dog/Aardwolf/sled-extensions" }
|
sled-extensions = { version = "0.3.0-alpha.0", features = ["bincode", "cbor"], git = "https://git.asonix.dog/Aardwolf/sled-extensions" }
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
|
|
61
src/lib.rs
61
src/lib.rs
|
@ -61,10 +61,11 @@
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! impl Job for MyJob {
|
//! impl Job for MyJob {
|
||||||
//! type Processor = MyProcessor; // We'll define this later
|
|
||||||
//! type State = ();
|
//! type State = ();
|
||||||
//! type Future = Ready<Result<(), Error>>;
|
//! type Future = Ready<Result<(), Error>>;
|
||||||
//!
|
//!
|
||||||
|
//! const NAME: &'static str = "MyJob";
|
||||||
|
//!
|
||||||
//! fn run(self, _: Self::State) -> Self::Future {
|
//! fn run(self, _: Self::State) -> Self::Future {
|
||||||
//! println!("args: {:?}", self);
|
//! println!("args: {:?}", self);
|
||||||
//!
|
//!
|
||||||
|
@ -97,10 +98,11 @@
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! impl Job for MyJob {
|
//! impl Job for MyJob {
|
||||||
//! type Processor = MyProcessor;
|
|
||||||
//! type State = MyState;
|
//! type State = MyState;
|
||||||
//! type Future = Ready<Result<(), Error>>;
|
//! type Future = Ready<Result<(), Error>>;
|
||||||
//!
|
//!
|
||||||
|
//! const NAME: &'static str = "MyJob";
|
||||||
|
//!
|
||||||
//! fn run(self, state: Self::State) -> Self::Future {
|
//! fn run(self, state: Self::State) -> Self::Future {
|
||||||
//! info!("{}: args, {:?}", state.app_name, self);
|
//! info!("{}: args, {:?}", state.app_name, self);
|
||||||
//!
|
//!
|
||||||
|
@ -109,47 +111,6 @@
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! #### Next, define a Processor.
|
|
||||||
//! Processors are types that define default attributes for jobs, as well as containing some logic
|
|
||||||
//! used internally to perform the job. Processors must implement `Proccessor` and `Clone`.
|
|
||||||
//!
|
|
||||||
//! ```rust,ignore
|
|
||||||
//! use background_jobs::{Backoff, MaxRetries, Processor};
|
|
||||||
//!
|
|
||||||
//! const DEFAULT_QUEUE: &'static str = "default";
|
|
||||||
//!
|
|
||||||
//! #[derive(Clone, Debug)]
|
|
||||||
//! pub struct MyProcessor;
|
|
||||||
//!
|
|
||||||
//! impl Processor for MyProcessor {
|
|
||||||
//! // The kind of job this processor should execute
|
|
||||||
//! type Job = MyJob;
|
|
||||||
//!
|
|
||||||
//! // The name of the processor. It is super important that each processor has a unique name,
|
|
||||||
//! // because otherwise one processor will overwrite another processor when they're being
|
|
||||||
//! // registered.
|
|
||||||
//! const NAME: &'static str = "MyProcessor";
|
|
||||||
//!
|
|
||||||
//! // The queue that this processor belongs to
|
|
||||||
//! //
|
|
||||||
//! // Workers have the option to subscribe to specific queues, so this is important to
|
|
||||||
//! // determine which worker will call the processor
|
|
||||||
//! //
|
|
||||||
//! // Jobs can optionally override the queue they're spawned on
|
|
||||||
//! const QUEUE: &'static str = DEFAULT_QUEUE;
|
|
||||||
//!
|
|
||||||
//! // The number of times background-jobs should try to retry a job before giving up
|
|
||||||
//! //
|
|
||||||
//! // Jobs can optionally override this value
|
|
||||||
//! const MAX_RETRIES: MaxRetries = MaxRetries::Count(1);
|
|
||||||
//!
|
|
||||||
//! // The logic to determine how often to retry this job if it fails
|
|
||||||
//! //
|
|
||||||
//! // Jobs can optionally override this value
|
|
||||||
//! const BACKOFF_STRATEGY: Backoff = Backoff::Exponential(2);
|
|
||||||
//! }
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! #### Running jobs
|
//! #### Running jobs
|
||||||
//! By default, this crate ships with the `background-jobs-actix` feature enabled. This uses the
|
//! By default, this crate ships with the `background-jobs-actix` feature enabled. This uses the
|
||||||
//! `background-jobs-actix` crate to spin up a Server and Workers, and provides a mechanism for
|
//! `background-jobs-actix` crate to spin up a Server and Workers, and provides a mechanism for
|
||||||
|
@ -179,14 +140,14 @@
|
||||||
//!
|
//!
|
||||||
//! // Configure and start our workers
|
//! // Configure and start our workers
|
||||||
//! WorkerConfig::new(move || MyState::new("My App"))
|
//! WorkerConfig::new(move || MyState::new("My App"))
|
||||||
//! .register(MyProcessor)
|
//! .register::<MyJob>()
|
||||||
//! .set_processor_count(DEFAULT_QUEUE, 16)
|
//! .set_processor_count(DEFAULT_QUEUE, 16)
|
||||||
//! .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
|
||||||
//! actix_rt::signal::ctrl_c().await?;
|
//! actix_rt::signal::ctrl_c().await?;
|
||||||
|
@ -200,12 +161,10 @@
|
||||||
//!
|
//!
|
||||||
//! #### Bringing your own server/worker implementation
|
//! #### Bringing your own server/worker implementation
|
||||||
//! If you want to create your own jobs processor based on this idea, you can depend on the
|
//! If you want to create your own jobs processor based on this idea, you can depend on the
|
||||||
//! `background-jobs-core` crate, which provides the Processor and Job traits, as well as some
|
//! `background-jobs-core` crate, which provides the Job trait, 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::{
|
pub use background_jobs_core::{memory_storage, Backoff, Job, JobStat, MaxRetries, Stats};
|
||||||
memory_storage, Backoff, Job, JobStat, MaxRetries, Processor, Stats,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(feature = "background-jobs-actix")]
|
#[cfg(feature = "background-jobs-actix")]
|
||||||
pub use background_jobs_actix::{create_server, ActixJob, QueueHandle, WorkerConfig};
|
pub use background_jobs_actix::{create_server, ActixJob, QueueHandle, WorkerConfig};
|
||||||
|
|
Loading…
Reference in a new issue