use core::fmt;
use core::time::Duration;
use std::sync::Arc;
use crate::spec::{LeaseId, MissPolicy, PoolId, Priority, TaskId, TaskSpec};
use crate::time::Time;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum TaskStatus {
Blocked,
Scheduled,
Ready,
Running,
Retrying,
Succeeded,
Failed,
DeadLettered,
Cancelled,
Skipped,
Missed,
Shed,
}
impl TaskStatus {
pub fn is_terminal(self) -> bool {
!matches!(
self,
TaskStatus::Blocked
| TaskStatus::Scheduled
| TaskStatus::Ready
| TaskStatus::Running
| TaskStatus::Retrying
)
}
pub fn name(self) -> &'static str {
match self {
TaskStatus::Blocked => "blocked",
TaskStatus::Scheduled => "scheduled",
TaskStatus::Ready => "ready",
TaskStatus::Running => "running",
TaskStatus::Retrying => "retrying",
TaskStatus::Succeeded => "succeeded",
TaskStatus::Failed => "failed",
TaskStatus::DeadLettered => "dead_lettered",
TaskStatus::Cancelled => "cancelled",
TaskStatus::Skipped => "skipped",
TaskStatus::Missed => "missed",
TaskStatus::Shed => "shed",
}
}
pub fn from_name(s: &str) -> Option<TaskStatus> {
use TaskStatus::*;
Some(match s {
"blocked" => Blocked,
"scheduled" => Scheduled,
"ready" => Ready,
"running" => Running,
"retrying" => Retrying,
"succeeded" => Succeeded,
"failed" => Failed,
"dead_lettered" => DeadLettered,
"cancelled" => Cancelled,
"skipped" => Skipped,
"missed" => Missed,
"shed" => Shed,
_ => return None,
})
}
}
impl fmt::Display for TaskStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Outcome {
Success,
Failure {
retryable: bool,
retry_after: Option<Duration>,
message: Arc<str>,
},
Cancelled,
}
impl Outcome {
pub fn retryable(msg: impl Into<Arc<str>>) -> Outcome {
Outcome::Failure {
retryable: true,
retry_after: None,
message: msg.into(),
}
}
pub fn permanent(msg: impl Into<Arc<str>>) -> Outcome {
Outcome::Failure {
retryable: false,
retry_after: None,
message: msg.into(),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SubmitError {
UnknownPool(Arc<str>),
UnknownResource(Arc<str>),
ResourceExceedsCapacity(Arc<str>),
ReservationConflict,
Cycle,
UnknownDependency(Arc<str>),
Full,
Overloaded,
DuplicateKeyInBatch(Arc<str>),
Invalid(&'static str),
}
impl fmt::Display for SubmitError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SubmitError::UnknownPool(p) => write!(f, "unknown pool '{p}'"),
SubmitError::UnknownResource(r) => write!(f, "unknown resource '{r}'"),
SubmitError::ResourceExceedsCapacity(r) => {
write!(f, "requested amount exceeds capacity of resource '{r}'")
}
SubmitError::ReservationConflict => f.write_str("fixed-start reservation conflict"),
SubmitError::Cycle => f.write_str("dependency cycle"),
SubmitError::UnknownDependency(k) => write!(f, "unknown dependency '{k}'"),
SubmitError::Full => f.write_str("queue full"),
SubmitError::Overloaded => f.write_str("overloaded"),
SubmitError::DuplicateKeyInBatch(k) => write!(f, "duplicate key '{k}' in batch"),
SubmitError::Invalid(m) => write!(f, "invalid task: {m}"),
}
}
}
impl std::error::Error for SubmitError {}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Submitted {
pub id: TaskId,
pub duplicate: bool,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum AbortReason {
Timeout,
Cancelled,
LeaseExpired,
}
#[derive(Debug)]
pub struct Dispatch<P> {
pub lease: LeaseId,
pub id: TaskId,
pub pool: PoolId,
pub attempt: u32,
pub payload: Arc<P>,
pub kind: Arc<str>,
pub key: Option<Arc<str>>,
pub timeout: Option<Duration>,
pub deadline: Option<Time>,
pub tags: crate::spec::Tags,
pub group: Labels,
}
pub type Labels = Arc<[(Arc<str>, Arc<str>)]>;
impl<P> Clone for Dispatch<P> {
fn clone(&self) -> Self {
Dispatch {
lease: self.lease,
id: self.id,
pool: self.pool,
attempt: self.attempt,
payload: self.payload.clone(),
kind: self.kind.clone(),
key: self.key.clone(),
timeout: self.timeout,
deadline: self.deadline,
tags: self.tags.clone(),
group: self.group.clone(),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct StatusEvent {
pub id: TaskId,
pub pool: PoolId,
pub key: Option<Arc<str>>,
pub status: TaskStatus,
pub attempt: u32,
pub at: Time,
pub message: Option<Arc<str>>,
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum OverlapPolicy {
#[default]
Skip,
Allow,
Queue,
QueueOnSuccess,
}
#[derive(Debug)]
pub struct RecurringSpec<P> {
pub name: Arc<str>,
pub cron: Arc<str>,
pub tz: Option<Arc<str>>,
pub template: TaskSpec<P>,
pub deadline_after: Option<Duration>,
pub overlap: OverlapPolicy,
pub tolerance: Duration,
pub on_miss: MissPolicy,
}
impl<P> Clone for RecurringSpec<P> {
fn clone(&self) -> Self {
RecurringSpec {
name: self.name.clone(),
cron: self.cron.clone(),
tz: self.tz.clone(),
template: self.template.clone(),
deadline_after: self.deadline_after,
overlap: self.overlap,
tolerance: self.tolerance,
on_miss: self.on_miss,
}
}
}
#[derive(Debug)]
pub enum Mutation<P> {
Submitted {
id: TaskId,
seq: u64,
arrival: Time,
spec: TaskSpec<P>,
},
Status {
id: TaskId,
status: TaskStatus,
attempt: u32,
at: Time,
message: Option<Arc<str>>,
},
RecurringUpsert {
spec: RecurringSpec<P>,
},
RecurringRemove {
name: Arc<str>,
},
RecurringNext {
name: Arc<str>,
next: Option<Time>,
last: Option<TaskId>,
},
}
impl<P> Clone for Mutation<P> {
fn clone(&self) -> Self {
match self {
Mutation::Submitted {
id,
seq,
arrival,
spec,
} => Mutation::Submitted {
id: *id,
seq: *seq,
arrival: *arrival,
spec: spec.clone(),
},
Mutation::Status {
id,
status,
attempt,
at,
message,
} => Mutation::Status {
id: *id,
status: *status,
attempt: *attempt,
at: *at,
message: message.clone(),
},
Mutation::RecurringUpsert { spec } => Mutation::RecurringUpsert { spec: spec.clone() },
Mutation::RecurringRemove { name } => Mutation::RecurringRemove { name: name.clone() },
Mutation::RecurringNext { name, next, last } => Mutation::RecurringNext {
name: name.clone(),
next: *next,
last: *last,
},
}
}
}
#[derive(Debug)]
pub enum Action<P> {
Dispatch(Dispatch<P>),
Abort {
lease: LeaseId,
id: TaskId,
reason: AbortReason,
},
ScaleTo {
pool: PoolId,
desired: u32,
},
Status(StatusEvent),
Persist(Box<Mutation<P>>),
SpaceAvailable,
}
#[derive(Debug)]
pub struct RecoveredTask<P> {
pub id: TaskId,
pub seq: u64,
pub arrival: Time,
pub spec: TaskSpec<P>,
pub attempt: u32,
}
#[derive(Clone, Debug)]
pub struct RecoveredTerminal {
pub id: TaskId,
pub key: Option<Arc<str>>,
pub status: TaskStatus,
pub at: Time,
}
#[derive(Debug)]
pub struct RecoveredRecurring<P> {
pub spec: RecurringSpec<P>,
pub next: Option<Time>,
pub last: Option<TaskId>,
}
#[derive(Debug)]
pub struct Recovered<P> {
pub tasks: Vec<RecoveredTask<P>>,
pub terminal: Vec<RecoveredTerminal>,
pub recurring: Vec<RecoveredRecurring<P>>,
pub next_id: u64,
}
impl<P> Recovered<P> {
pub fn outside_shard(&self, shard: u8) -> Option<TaskId> {
let last = (self.next_id > 0).then(|| TaskId(self.next_id - 1));
last.into_iter()
.chain(self.tasks.iter().map(|t| t.id))
.chain(self.terminal.iter().map(|t| t.id))
.find(|id| id.shard() != shard)
}
}
impl<P> Default for Recovered<P> {
fn default() -> Self {
Recovered {
tasks: Vec::new(),
terminal: Vec::new(),
recurring: Vec::new(),
next_id: 0,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct TaskSummary {
pub id: TaskId,
pub key: Option<Arc<str>>,
pub pool: Arc<str>,
pub kind: Arc<str>,
pub priority: Priority,
pub status: TaskStatus,
pub attempt: u32,
pub tenant: Option<Arc<str>>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct RecurringInfo {
pub name: Arc<str>,
pub cron: Arc<str>,
pub tz: Option<Arc<str>>,
pub pool: Arc<str>,
pub kind: Arc<str>,
pub overlap: OverlapPolicy,
pub next: Option<Time>,
pub last_task: Option<TaskId>,
}