169 lines
5.8 KiB
Rust
169 lines
5.8 KiB
Rust
// file: crates/ksp-job-api/src/lifecycle.rs
|
|
// version: 1
|
|
|
|
/// Completion classification of a Job that reached its normal terminal state.
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
#[non_exhaustive]
|
|
pub enum JobCompletion {
|
|
/// Every selected candidate or unit of work reached a durable complete result.
|
|
Complete,
|
|
/// The Job terminated normally while retaining one or more explicitly observable gaps.
|
|
Partial,
|
|
}
|
|
|
|
impl JobCompletion {
|
|
/// Returns the stable safe code for this completion classification.
|
|
#[must_use]
|
|
pub const fn code(&self) -> &'static str {
|
|
return match self {
|
|
Self::Complete => "complete",
|
|
Self::Partial => "partial",
|
|
};
|
|
}
|
|
}
|
|
|
|
/// Current lifecycle state of one bounded Job.
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
#[non_exhaustive]
|
|
pub enum JobState {
|
|
/// The Job was admitted but has not started producing work.
|
|
Created,
|
|
/// The Job is actively producing or draining normal work.
|
|
Running,
|
|
/// Cancellation was observed and new work must no longer be admitted.
|
|
Cancelling,
|
|
/// The Job reached a normal terminal state with an explicit completion classification.
|
|
Completed(crate::JobCompletion),
|
|
/// The Job reached its cooperative cancellation terminal state.
|
|
Cancelled,
|
|
/// The Job reached a terminal failure state.
|
|
Failed,
|
|
}
|
|
|
|
impl JobState {
|
|
/// Returns the stable safe lifecycle code without rendering Job data.
|
|
#[must_use]
|
|
pub const fn code(&self) -> &'static str {
|
|
return match self {
|
|
Self::Created => "created",
|
|
Self::Running => "running",
|
|
Self::Cancelling => "cancelling",
|
|
Self::Completed(_) => "completed",
|
|
Self::Cancelled => "cancelled",
|
|
Self::Failed => "failed",
|
|
};
|
|
}
|
|
|
|
/// Returns the normal completion classification when the state is [`Self::Completed`].
|
|
#[must_use]
|
|
pub const fn completion(&self) -> std::option::Option<crate::JobCompletion> {
|
|
return match self {
|
|
Self::Completed(completion) => std::option::Option::Some(*completion),
|
|
_ => std::option::Option::None,
|
|
};
|
|
}
|
|
|
|
/// Reports whether no later lifecycle transition is permitted.
|
|
#[must_use]
|
|
pub const fn is_terminal(&self) -> bool {
|
|
return matches!(self, Self::Completed(_) | Self::Cancelled | Self::Failed);
|
|
}
|
|
}
|
|
|
|
/// Passive owner of one Job identity and its validated lifecycle state.
|
|
#[derive(Eq, PartialEq)]
|
|
pub struct JobLifecycle {
|
|
id: crate::JobId,
|
|
kind: crate::JobKindCode,
|
|
state: crate::JobState,
|
|
}
|
|
|
|
impl JobLifecycle {
|
|
/// Creates one lifecycle in [`JobState::Created`] state.
|
|
#[must_use]
|
|
pub const fn new(id: crate::JobId, kind: crate::JobKindCode) -> Self {
|
|
return Self { id, kind, state: crate::JobState::Created };
|
|
}
|
|
|
|
/// Returns the logical Job identity.
|
|
#[must_use]
|
|
pub const fn id(&self) -> &crate::JobId {
|
|
return &self.id;
|
|
}
|
|
|
|
/// Returns the stable Job family code.
|
|
#[must_use]
|
|
pub const fn kind(&self) -> &crate::JobKindCode {
|
|
return &self.kind;
|
|
}
|
|
|
|
/// Returns the current lifecycle state.
|
|
#[must_use]
|
|
pub const fn state(&self) -> crate::JobState {
|
|
return self.state;
|
|
}
|
|
|
|
/// Transitions a newly created Job into its running state.
|
|
pub fn start(&mut self) -> crate::Result<()> {
|
|
return self.transition(crate::JobState::Running);
|
|
}
|
|
|
|
/// Records that a running Job observed cooperative cancellation intent.
|
|
pub fn mark_cancelling(&mut self) -> crate::Result<()> {
|
|
return self.transition(crate::JobState::Cancelling);
|
|
}
|
|
|
|
/// Completes a running or cancelling Job with its explicit normal outcome.
|
|
pub fn complete(&mut self, completion: crate::JobCompletion) -> crate::Result<()> {
|
|
return self.transition(crate::JobState::Completed(completion));
|
|
}
|
|
|
|
/// Marks a created or cancelling Job as cooperatively cancelled.
|
|
pub fn mark_cancelled(&mut self) -> crate::Result<()> {
|
|
return self.transition(crate::JobState::Cancelled);
|
|
}
|
|
|
|
/// Marks a running or cancelling Job as failed.
|
|
pub fn fail(&mut self) -> crate::Result<()> {
|
|
return self.transition(crate::JobState::Failed);
|
|
}
|
|
|
|
fn transition(&mut self, target: crate::JobState) -> crate::Result<()> {
|
|
if !allowed_transition(self.state, target) {
|
|
return std::result::Result::Err(transition_error(self.state, target));
|
|
}
|
|
self.state = target;
|
|
return std::result::Result::Ok(());
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Debug for JobLifecycle {
|
|
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
return formatter.debug_struct("JobLifecycle").field("id", &self.id).field("kind", &self.kind).field("state", &self.state).finish();
|
|
}
|
|
}
|
|
|
|
fn allowed_transition(source: crate::JobState, target: crate::JobState) -> bool {
|
|
return matches!(
|
|
(source, target),
|
|
(crate::JobState::Created, crate::JobState::Running)
|
|
| (crate::JobState::Created, crate::JobState::Cancelled)
|
|
| (crate::JobState::Running, crate::JobState::Cancelling)
|
|
| (crate::JobState::Running, crate::JobState::Completed(_))
|
|
| (crate::JobState::Running, crate::JobState::Failed)
|
|
| (crate::JobState::Cancelling, crate::JobState::Completed(_))
|
|
| (crate::JobState::Cancelling, crate::JobState::Cancelled)
|
|
| (crate::JobState::Cancelling, crate::JobState::Failed)
|
|
);
|
|
}
|
|
|
|
fn transition_error(source: crate::JobState, target: crate::JobState) -> crate::Error {
|
|
return crate::Error::new(crate::ERROR_CODE_JOB_TRANSITION_INVALID, "invalid Job lifecycle transition")
|
|
.with_context("source_state", source.code())
|
|
.with_context("target_state", target.code());
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[path = "../unit_tests/lifecycle.rs"]
|
|
mod tests;
|