mirror of
https://git.asonix.dog/asonix/background-jobs.git
synced 2024-11-25 05:21:00 +00:00
jobs-core: remove JobStatus, constify some methods, return whether job is finished in complete
This commit is contained in:
parent
6cec89361c
commit
3045f003b7
4 changed files with 53 additions and 95 deletions
|
@ -92,6 +92,11 @@ impl NewJobInfo {
|
||||||
&self.queue
|
&self.queue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The name of this job
|
||||||
|
pub fn name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether this job is ready to be run immediately
|
/// Whether this job is ready to be run immediately
|
||||||
pub fn is_ready(&self) -> bool {
|
pub fn is_ready(&self) -> bool {
|
||||||
self.next_queue.is_none()
|
self.next_queue.is_none()
|
||||||
|
|
|
@ -40,7 +40,7 @@ pub enum JobError {
|
||||||
Unregistered,
|
Unregistered,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
/// Indicate the state of a job after an attempted run
|
/// Indicate the state of a job after an attempted run
|
||||||
pub enum JobResult {
|
pub enum JobResult {
|
||||||
/// The job succeeded
|
/// The job succeeded
|
||||||
|
@ -58,100 +58,42 @@ pub enum JobResult {
|
||||||
|
|
||||||
impl JobResult {
|
impl JobResult {
|
||||||
/// Indicate a successful job
|
/// Indicate a successful job
|
||||||
pub fn success() -> Self {
|
pub const fn success() -> Self {
|
||||||
JobResult::Success
|
JobResult::Success
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Indicate a failed job
|
/// Indicate a failed job
|
||||||
pub fn failure() -> Self {
|
pub const fn failure() -> Self {
|
||||||
JobResult::Failure
|
JobResult::Failure
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Indicate that the job was not registered for this worker
|
/// Indicate that the job was not registered for this worker
|
||||||
pub fn unregistered() -> Self {
|
pub const fn unregistered() -> Self {
|
||||||
JobResult::Unregistered
|
JobResult::Unregistered
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the job failed
|
/// Check if the job failed
|
||||||
pub fn is_failure(&self) -> bool {
|
pub const fn is_failure(self) -> bool {
|
||||||
*self == JobResult::Failure
|
matches!(self, JobResult::Failure)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the job succeeded
|
/// Check if the job succeeded
|
||||||
pub fn is_success(&self) -> bool {
|
pub const fn is_success(self) -> bool {
|
||||||
*self == JobResult::Success
|
matches!(self, JobResult::Success)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the job is missing it's processor
|
/// Check if the job is missing it's processor
|
||||||
pub fn is_unregistered(&self) -> bool {
|
pub const fn is_unregistered(self) -> bool {
|
||||||
*self == JobResult::Unregistered
|
matches!(self, JobResult::Unregistered)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the job was returned without an execution attempt
|
/// Check if the job was returned without an execution attempt
|
||||||
pub fn is_unexecuted(&self) -> bool {
|
pub const fn is_unexecuted(self) -> bool {
|
||||||
*self == JobResult::Unexecuted
|
matches!(self, JobResult::Unexecuted)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
/// Set the status of a job when storing it
|
|
||||||
pub enum JobStatus {
|
|
||||||
/// Job should be queued
|
|
||||||
Pending,
|
|
||||||
|
|
||||||
/// Job is running
|
|
||||||
Running,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl JobStatus {
|
|
||||||
/// The job should be queued
|
|
||||||
pub fn pending() -> Self {
|
|
||||||
JobStatus::Pending
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The job is running
|
|
||||||
pub fn running() -> Self {
|
|
||||||
JobStatus::Running
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if the job is ready to be queued
|
|
||||||
pub fn is_pending(&self) -> bool {
|
|
||||||
*self == JobStatus::Pending
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if the job is running
|
|
||||||
pub fn is_running(&self) -> bool {
|
|
||||||
*self == JobStatus::Running
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for JobStatus {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
JobStatus::Pending => write!(f, "Pending"),
|
|
||||||
JobStatus::Running => write!(f, "Running"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, thiserror::Error)]
|
|
||||||
#[error("Invalid job status")]
|
|
||||||
/// The error generated when parsing a job's status if it's not 'Pending' or 'Running'
|
|
||||||
pub struct JobStatusError;
|
|
||||||
|
|
||||||
impl std::str::FromStr for JobStatus {
|
|
||||||
type Err = JobStatusError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
match s {
|
|
||||||
"Pending" => Ok(JobStatus::Pending),
|
|
||||||
"Running" => Ok(JobStatus::Running),
|
|
||||||
_ => Err(JobStatusError),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
|
||||||
/// Different styles for retrying jobs
|
/// Different styles for retrying jobs
|
||||||
pub enum Backoff {
|
pub enum Backoff {
|
||||||
/// Seconds between execution
|
/// Seconds between execution
|
||||||
|
@ -168,7 +110,7 @@ pub enum Backoff {
|
||||||
Exponential(usize),
|
Exponential(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
/// How many times a job should be retried before giving up
|
/// How many times a job should be retried before giving up
|
||||||
pub enum MaxRetries {
|
pub enum MaxRetries {
|
||||||
/// Keep retrying forever
|
/// Keep retrying forever
|
||||||
|
@ -179,11 +121,11 @@ pub enum MaxRetries {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MaxRetries {
|
impl MaxRetries {
|
||||||
fn compare(&self, retry_count: u32) -> ShouldStop {
|
fn compare(self, retry_count: u32) -> ShouldStop {
|
||||||
match *self {
|
match self {
|
||||||
MaxRetries::Infinite => ShouldStop::Requeue,
|
MaxRetries::Infinite => ShouldStop::Requeue,
|
||||||
MaxRetries::Count(ref count) => {
|
MaxRetries::Count(count) => {
|
||||||
if (retry_count as usize) <= *count {
|
if (retry_count as usize) <= count {
|
||||||
ShouldStop::Requeue
|
ShouldStop::Requeue
|
||||||
} else {
|
} else {
|
||||||
ShouldStop::LimitReached
|
ShouldStop::LimitReached
|
||||||
|
@ -193,7 +135,7 @@ impl MaxRetries {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
/// A type that represents whether a job should be requeued
|
/// A type that represents whether a job should be requeued
|
||||||
pub enum ShouldStop {
|
pub enum ShouldStop {
|
||||||
/// The job has hit the maximum allowed number of retries, and should be failed permanently
|
/// The job has hit the maximum allowed number of retries, and should be failed permanently
|
||||||
|
@ -205,8 +147,8 @@ pub enum ShouldStop {
|
||||||
|
|
||||||
impl ShouldStop {
|
impl ShouldStop {
|
||||||
/// A boolean representation of this state
|
/// A boolean representation of this state
|
||||||
pub fn should_requeue(&self) -> bool {
|
pub const fn should_requeue(&self) -> bool {
|
||||||
*self == ShouldStop::Requeue
|
matches!(self, ShouldStop::Requeue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -165,6 +165,8 @@ where
|
||||||
{
|
{
|
||||||
let args = job.args.clone();
|
let args = job.args.clone();
|
||||||
let id = job.id;
|
let id = job.id;
|
||||||
|
let name = job.name.clone();
|
||||||
|
let queue = job.queue.clone();
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
|
||||||
|
@ -177,12 +179,12 @@ where
|
||||||
|
|
||||||
let span = Span::current();
|
let span = Span::current();
|
||||||
span.record("job.execution_time", &tracing::field::display(&seconds));
|
span.record("job.execution_time", &tracing::field::display(&seconds));
|
||||||
metrics::histogram!("background-jobs.job.execution_time", "queue" => job.queue.clone(), "name" => job.name.clone()).record(seconds);
|
metrics::histogram!("background-jobs.job.execution_time", "queue" => queue.clone(), "name" => name.clone()).record(seconds);
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(Ok(_)) => {
|
Ok(Ok(_)) => {
|
||||||
#[cfg(feature = "completion-logging")]
|
#[cfg(feature = "completion-logging")]
|
||||||
tracing::info!("Job completed");
|
tracing::info!("Job {queue}: {name}-{id} completed");
|
||||||
|
|
||||||
ReturnJobInfo::pass(id)
|
ReturnJobInfo::pass(id)
|
||||||
}
|
}
|
||||||
|
@ -192,7 +194,7 @@ where
|
||||||
span.record("exception.message", &tracing::field::display(&display));
|
span.record("exception.message", &tracing::field::display(&display));
|
||||||
span.record("exception.details", &tracing::field::display(&debug));
|
span.record("exception.details", &tracing::field::display(&debug));
|
||||||
#[cfg(feature = "error-logging")]
|
#[cfg(feature = "error-logging")]
|
||||||
tracing::warn!("Job errored");
|
tracing::warn!("Job {queue}: {name}-{id} errored");
|
||||||
ReturnJobInfo::fail(id)
|
ReturnJobInfo::fail(id)
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
|
@ -205,7 +207,7 @@ where
|
||||||
&tracing::field::display("Job panicked"),
|
&tracing::field::display("Job panicked"),
|
||||||
);
|
);
|
||||||
#[cfg(feature = "error-logging")]
|
#[cfg(feature = "error-logging")]
|
||||||
tracing::warn!("Job panicked");
|
tracing::warn!("Job {queue}: {name}-{id} panicked");
|
||||||
ReturnJobInfo::fail(id)
|
ReturnJobInfo::fail(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,9 @@ pub trait Storage: Clone + Send {
|
||||||
/// The error type used by the storage mechansim.
|
/// The error type used by the storage mechansim.
|
||||||
type Error: Error + Send + Sync;
|
type Error: Error + Send + Sync;
|
||||||
|
|
||||||
|
/// Get the JobInfo for a given job ID
|
||||||
|
async fn info(&self, job_id: Uuid) -> Result<Option<JobInfo>, Self::Error>;
|
||||||
|
|
||||||
/// push a job into the queue
|
/// push a job into the queue
|
||||||
async fn push(&self, job: NewJobInfo) -> Result<Uuid, Self::Error>;
|
async fn push(&self, job: NewJobInfo) -> Result<Uuid, Self::Error>;
|
||||||
|
|
||||||
|
@ -23,7 +26,9 @@ pub trait Storage: Clone + Send {
|
||||||
async fn heartbeat(&self, job_id: Uuid, runner_id: Uuid) -> Result<(), Self::Error>;
|
async fn heartbeat(&self, job_id: Uuid, runner_id: Uuid) -> Result<(), Self::Error>;
|
||||||
|
|
||||||
/// "Return" a job to the database, marking it for retry if needed
|
/// "Return" a job to the database, marking it for retry if needed
|
||||||
async fn complete(&self, return_job_info: ReturnJobInfo) -> Result<(), Self::Error>;
|
///
|
||||||
|
/// returns `true` if the job has not been requeued
|
||||||
|
async fn complete(&self, return_job_info: ReturnJobInfo) -> Result<bool, Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A default, in-memory implementation of a storage mechanism
|
/// A default, in-memory implementation of a storage mechanism
|
||||||
|
@ -83,6 +88,10 @@ pub mod memory_storage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get(&self, job_id: Uuid) -> Option<JobInfo> {
|
||||||
|
self.inner.lock().unwrap().jobs.get(&job_id).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
fn listener(&self, pop_queue: String) -> (Pin<Box<EventListener>>, Duration) {
|
fn listener(&self, pop_queue: String) -> (Pin<Box<EventListener>>, Duration) {
|
||||||
let lower_bound = Uuid::new_v7(Timestamp::from_unix(NoContext, 0, 0));
|
let lower_bound = Uuid::new_v7(Timestamp::from_unix(NoContext, 0, 0));
|
||||||
let now = OffsetDateTime::now_utc();
|
let now = OffsetDateTime::now_utc();
|
||||||
|
@ -216,6 +225,10 @@ pub mod memory_storage {
|
||||||
impl<T: Timer + Send + Sync + Clone> super::Storage for Storage<T> {
|
impl<T: Timer + Send + Sync + Clone> super::Storage for Storage<T> {
|
||||||
type Error = Infallible;
|
type Error = Infallible;
|
||||||
|
|
||||||
|
async fn info(&self, job_id: Uuid) -> Result<Option<JobInfo>, Self::Error> {
|
||||||
|
Ok(self.get(job_id))
|
||||||
|
}
|
||||||
|
|
||||||
/// push a job into the queue
|
/// push a job into the queue
|
||||||
async fn push(&self, job: NewJobInfo) -> Result<Uuid, Self::Error> {
|
async fn push(&self, job: NewJobInfo) -> Result<Uuid, Self::Error> {
|
||||||
Ok(self.insert(job.build()))
|
Ok(self.insert(job.build()))
|
||||||
|
@ -251,29 +264,25 @@ pub mod memory_storage {
|
||||||
async fn complete(
|
async fn complete(
|
||||||
&self,
|
&self,
|
||||||
ReturnJobInfo { id, result }: ReturnJobInfo,
|
ReturnJobInfo { id, result }: ReturnJobInfo,
|
||||||
) -> Result<(), Self::Error> {
|
) -> Result<bool, Self::Error> {
|
||||||
let mut job = if let Some(job) = self.remove_job(id) {
|
let mut job = if let Some(job) = self.remove_job(id) {
|
||||||
job
|
job
|
||||||
} else {
|
} else {
|
||||||
return Ok(());
|
return Ok(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
JobResult::Success => {
|
JobResult::Success => Ok(true),
|
||||||
// nothing
|
JobResult::Unregistered | JobResult::Unexecuted => Ok(true),
|
||||||
}
|
|
||||||
JobResult::Unregistered | JobResult::Unexecuted => {
|
|
||||||
// do stuff...
|
|
||||||
}
|
|
||||||
JobResult::Failure => {
|
JobResult::Failure => {
|
||||||
// requeue
|
|
||||||
if job.prepare_retry() {
|
if job.prepare_retry() {
|
||||||
self.insert(job);
|
self.insert(job);
|
||||||
|
return Ok(false);
|
||||||
|
} else {
|
||||||
|
Ok(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue