use std::sync::Arc;
use std::time::Duration;
use crate::{
core::SupervisorConfig, policies::BackoffPolicy, policies::RestartPolicy, tasks::task::TaskRef,
};
#[derive(Clone)]
#[must_use]
pub struct TaskSpec {
timeout: Option<Duration>,
restart: RestartPolicy,
backoff: BackoffPolicy,
task: TaskRef,
slot: Option<Arc<str>>,
max_retries: u32,
}
impl std::fmt::Debug for TaskSpec {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TaskSpec")
.field("restart", &self.restart)
.field("backoff", &self.backoff)
.field("timeout", &self.timeout)
.field("task", &self.task.name())
.field("slot", &self.slot())
.field("max_retries", &self.max_retries)
.finish()
}
}
impl TaskSpec {
pub fn new(
task: TaskRef,
restart: RestartPolicy,
backoff: BackoffPolicy,
timeout: Option<Duration>,
) -> Self {
Self {
restart,
backoff,
timeout,
task,
slot: None,
max_retries: 0,
}
}
pub fn once(task: TaskRef) -> Self {
Self {
restart: RestartPolicy::Never,
backoff: BackoffPolicy::default(),
timeout: None,
task,
slot: None,
max_retries: 0,
}
}
pub fn restartable(task: TaskRef) -> Self {
Self {
restart: RestartPolicy::OnFailure,
backoff: BackoffPolicy::default(),
timeout: None,
task,
slot: None,
max_retries: 0,
}
}
pub fn with_defaults(task: TaskRef, cfg: &SupervisorConfig) -> Self {
Self {
restart: cfg.restart,
backoff: cfg.backoff,
timeout: cfg.default_timeout(),
task,
slot: None,
max_retries: cfg.max_retries,
}
}
pub fn task(&self) -> &TaskRef {
&self.task
}
pub fn name(&self) -> &str {
self.task.name()
}
pub fn slot(&self) -> &str {
self.slot.as_deref().unwrap_or_else(|| self.task.name())
}
pub fn restart(&self) -> RestartPolicy {
self.restart
}
pub fn backoff(&self) -> BackoffPolicy {
self.backoff
}
pub fn timeout(&self) -> Option<Duration> {
self.timeout
}
pub fn max_retries(&self) -> u32 {
self.max_retries
}
pub fn with_timeout(mut self, timeout: Option<Duration>) -> Self {
self.timeout = timeout;
self
}
pub fn with_backoff(mut self, backoff: BackoffPolicy) -> Self {
self.backoff = backoff;
self
}
pub fn with_restart(mut self, restart: RestartPolicy) -> Self {
self.restart = restart;
self
}
pub fn with_max_retries(mut self, max_retries: u32) -> Self {
self.max_retries = max_retries;
self
}
pub fn with_slot(mut self, slot: impl Into<Arc<str>>) -> Self {
self.slot = Some(slot.into());
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::TaskFn;
use tokio_util::sync::CancellationToken;
fn task(name: &str) -> TaskRef {
TaskFn::arc(name, |_ctx: CancellationToken| async { Ok(()) })
}
#[test]
fn slot_falls_back_to_task_name() {
let spec = TaskSpec::once(task("my-task"));
assert_eq!(spec.slot(), "my-task");
}
#[test]
fn with_slot_overrides_slot_but_not_name() {
let spec = TaskSpec::once(task("runner-web-7")).with_slot("web");
assert_eq!(spec.slot(), "web");
assert_eq!(spec.name(), "runner-web-7");
}
}