use crate::child_runner::run_exit::TaskExit;
use crate::id::types::{ChildId, ChildStartCount, Generation, SupervisorPath};
use crate::readiness::signal::ReadinessState;
use crate::runtime::admission::AdmissionConflict;
use serde::{Deserialize, Serialize};
use std::time::Duration;
use uuid::Uuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ChildAttemptStatus {
Starting,
Running,
Ready,
Cancelling,
Stopped,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ChildControlOperation {
Active,
Paused,
Quarantined,
Removed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ChildStopState {
Idle,
NoActiveAttempt,
CancelDelivered,
Completed,
Failed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ChildControlFailurePhase {
WaitCompletion,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ChildControlFailure {
pub phase: ChildControlFailurePhase,
pub reason: String,
pub recoverable: bool,
}
impl ChildControlFailure {
pub fn new(
phase: ChildControlFailurePhase,
reason: impl Into<String>,
recoverable: bool,
) -> Self {
Self {
phase,
reason: reason.into(),
recoverable,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum GenerationFencePhase {
#[default]
Open,
WaitingForOldStop,
AbortingOld,
ReadyToStart,
Closed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum GenerationFenceDecision {
StartedImmediately,
QueuedAfterStop,
AlreadyPending,
BlockedByShutdown,
Rejected,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum StaleReportHandling {
IgnoredForState,
RecordedForAudit,
CountedForMetrics,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct StaleAttemptReport {
pub child_id: ChildId,
pub reported_generation: Generation,
pub reported_attempt: ChildStartCount,
pub current_generation: Option<Generation>,
pub current_attempt: Option<ChildStartCount>,
pub exit_kind: TaskExit,
pub handled_as: StaleReportHandling,
pub observed_at_unix_nanos: u128,
}
impl StaleAttemptReport {
#[allow(clippy::too_many_arguments)]
pub fn new(
child_id: ChildId,
reported_generation: Generation,
reported_attempt: ChildStartCount,
current_generation: Option<Generation>,
current_attempt: Option<ChildStartCount>,
exit_kind: TaskExit,
handled_as: StaleReportHandling,
observed_at_unix_nanos: u128,
) -> Self {
Self {
child_id,
reported_generation,
reported_attempt,
current_generation,
current_attempt,
exit_kind,
handled_as,
observed_at_unix_nanos,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PendingRestart {
pub command_id: Uuid,
pub requested_by: String,
pub reason: String,
pub old_generation: Generation,
pub old_attempt: ChildStartCount,
pub target_generation: Generation,
pub requested_at_unix_nanos: u128,
pub stop_deadline_at_unix_nanos: u128,
pub abort_requested: bool,
pub duplicate_request_count: u32,
}
impl PendingRestart {
#[allow(clippy::too_many_arguments)]
pub fn new(
command_id: Uuid,
requested_by: impl Into<String>,
reason: impl Into<String>,
old_generation: Generation,
old_attempt: ChildStartCount,
target_generation: Generation,
requested_at_unix_nanos: u128,
stop_deadline_at_unix_nanos: u128,
abort_requested: bool,
duplicate_request_count: u32,
) -> Self {
Self {
command_id,
requested_by: requested_by.into(),
reason: reason.into(),
old_generation,
old_attempt,
target_generation,
requested_at_unix_nanos,
stop_deadline_at_unix_nanos,
abort_requested,
duplicate_request_count,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GenerationFenceOutcome {
pub decision: GenerationFenceDecision,
pub old_generation: Option<Generation>,
pub old_attempt: Option<ChildStartCount>,
pub target_generation: Option<Generation>,
pub cancel_delivered: bool,
pub abort_requested: bool,
pub conflict: Option<ChildControlFailure>,
}
impl GenerationFenceOutcome {
pub fn new(
decision: GenerationFenceDecision,
old_generation: Option<Generation>,
old_attempt: Option<ChildStartCount>,
target_generation: Option<Generation>,
cancel_delivered: bool,
abort_requested: bool,
conflict: Option<ChildControlFailure>,
) -> Self {
Self {
decision,
old_generation,
old_attempt,
target_generation,
cancel_delivered,
abort_requested,
conflict,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GenerationFenceState {
pub phase: GenerationFencePhase,
pub active_generation: Option<Generation>,
pub active_attempt: Option<ChildStartCount>,
pub pending_restart: Option<PendingRestart>,
pub last_stale_report: Option<StaleAttemptReport>,
}
impl Default for GenerationFenceState {
fn default() -> Self {
Self {
phase: GenerationFencePhase::Open,
active_generation: None,
active_attempt: None,
pending_restart: None,
last_stale_report: None,
}
}
}
impl GenerationFenceState {
pub fn placeholder() -> Self {
Self::default()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PendingRestartSummary {
pub old_generation: Generation,
pub old_attempt: ChildStartCount,
pub target_generation: Generation,
}
impl From<&PendingRestart> for PendingRestartSummary {
fn from(source: &PendingRestart) -> Self {
Self {
old_generation: source.old_generation,
old_attempt: source.old_attempt,
target_generation: source.target_generation,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RestartLimitState {
pub window: Duration,
pub limit: u32,
pub used: u32,
pub remaining: u32,
pub exhausted: bool,
pub updated_at_unix_nanos: u128,
}
impl RestartLimitState {
pub fn new(window: Duration, limit: u32, used: u32, updated_at_unix_nanos: u128) -> Self {
let remaining = limit.saturating_sub(used);
Self {
window,
limit,
used,
remaining,
exhausted: remaining == 0,
updated_at_unix_nanos,
}
}
}
impl Default for RestartLimitState {
fn default() -> Self {
Self::new(Duration::from_secs(60), u32::MAX, 0, 0)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ChildLivenessState {
pub last_heartbeat_at_unix_nanos: Option<u128>,
pub heartbeat_stale: bool,
pub readiness: ReadinessState,
}
impl ChildLivenessState {
pub fn new(
last_heartbeat_at_unix_nanos: Option<u128>,
heartbeat_stale: bool,
readiness: ReadinessState,
) -> Self {
Self {
last_heartbeat_at_unix_nanos,
heartbeat_stale,
readiness,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ChildRuntimeRecord {
pub child_id: ChildId,
pub path: SupervisorPath,
pub generation: Option<Generation>,
pub attempt: Option<ChildStartCount>,
pub status: Option<ChildAttemptStatus>,
pub operation: ChildControlOperation,
pub liveness: ChildLivenessState,
pub restart_limit: RestartLimitState,
pub stop_state: ChildStopState,
pub failure: Option<ChildControlFailure>,
#[serde(default)]
pub generation_fence_phase: GenerationFencePhase,
#[serde(default)]
pub pending_restart: Option<PendingRestartSummary>,
}
impl ChildRuntimeRecord {
#[allow(clippy::too_many_arguments)]
pub fn new(
child_id: ChildId,
path: SupervisorPath,
generation: Option<Generation>,
attempt: Option<ChildStartCount>,
status: Option<ChildAttemptStatus>,
operation: ChildControlOperation,
liveness: ChildLivenessState,
restart_limit: RestartLimitState,
stop_state: ChildStopState,
failure: Option<ChildControlFailure>,
generation_fence_phase: GenerationFencePhase,
pending_restart: Option<PendingRestartSummary>,
) -> Self {
Self {
child_id,
path,
generation,
attempt,
status,
operation,
liveness,
restart_limit,
stop_state,
failure,
generation_fence_phase,
pending_restart,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ChildControlResult {
pub child_id: ChildId,
pub attempt: Option<ChildStartCount>,
pub generation: Option<Generation>,
pub operation_before: ChildControlOperation,
pub operation_after: ChildControlOperation,
pub status: Option<ChildAttemptStatus>,
pub cancel_delivered: bool,
pub stop_state: ChildStopState,
pub restart_limit: RestartLimitState,
pub liveness: ChildLivenessState,
pub idempotent: bool,
pub failure: Option<ChildControlFailure>,
#[serde(default)]
pub generation_fence: Option<GenerationFenceOutcome>,
#[serde(default)]
pub admission_conflict: Option<AdmissionConflict>,
}
impl ChildControlResult {
#[allow(clippy::too_many_arguments)]
pub fn new(
child_id: ChildId,
attempt: Option<ChildStartCount>,
generation: Option<Generation>,
operation_before: ChildControlOperation,
operation_after: ChildControlOperation,
status: Option<ChildAttemptStatus>,
cancel_delivered: bool,
stop_state: ChildStopState,
restart_limit: RestartLimitState,
liveness: ChildLivenessState,
idempotent: bool,
failure: Option<ChildControlFailure>,
generation_fence: Option<GenerationFenceOutcome>,
) -> Self {
Self {
child_id,
attempt,
generation,
operation_before,
operation_after,
status,
cancel_delivered,
stop_state,
restart_limit,
liveness,
idempotent,
failure,
generation_fence,
admission_conflict: None,
}
}
pub fn conflict(child_id: ChildId, conflict: AdmissionConflict) -> Self {
Self {
child_id,
attempt: Some(conflict.active_attempt),
generation: Some(conflict.active_generation),
operation_before: ChildControlOperation::Active,
operation_after: ChildControlOperation::Active,
status: Some(ChildAttemptStatus::Running),
cancel_delivered: false,
stop_state: ChildStopState::Idle,
restart_limit: RestartLimitState::default(),
liveness: ChildLivenessState::new(None, false, ReadinessState::Unreported),
idempotent: false,
failure: None,
generation_fence: None,
admission_conflict: Some(conflict),
}
}
}