use super::{Task, TaskResult};
use std::future::Future;
use std::pin::Pin;
type ShutdownReceiver = tokio::sync::oneshot::Receiver<()>;
type TaskFuture = Pin<Box<dyn Future<Output = TaskResult> + Send>>;
type TaskFutureFactory = Box<dyn FnOnce(ShutdownReceiver) -> TaskFuture + Send + 'static>;
pub struct SpawnTask {
name: String,
dependencies: Vec<String>,
priority: i32,
critical: bool,
future_fn: TaskFutureFactory,
}
impl SpawnTask {
pub fn new<Fut>(name: impl Into<String>, future: Fut) -> Self
where
Fut: Future<Output = TaskResult> + Send + 'static,
{
Self {
name: name.into(),
dependencies: Vec::new(),
priority: 0,
critical: false,
future_fn: Box::new(move |_shutdown_rx| Box::pin(future)),
}
}
pub fn with_shutdown<F, Fut>(name: impl Into<String>, future_fn: F) -> Self
where
F: FnOnce(tokio::sync::oneshot::Receiver<()>) -> Fut + Send + 'static,
Fut: Future<Output = TaskResult> + Send + 'static,
{
Self {
name: name.into(),
dependencies: Vec::new(),
priority: 0,
critical: false,
future_fn: Box::new(move |shutdown_rx| Box::pin(future_fn(shutdown_rx))),
}
}
pub fn with_dependencies(mut self, deps: Vec<String>) -> Self {
self.dependencies = deps;
self
}
pub fn with_priority(mut self, priority: i32) -> Self {
self.priority = priority;
self
}
pub fn with_critical(mut self, critical: bool) -> Self {
self.critical = critical;
self
}
}
impl Task for SpawnTask {
fn name(&self) -> &str {
&self.name
}
fn dependencies(&self) -> Vec<String> {
self.dependencies.clone()
}
fn run(
self: Box<Self>,
shutdown_rx: tokio::sync::oneshot::Receiver<()>,
) -> Pin<Box<dyn Future<Output = TaskResult> + Send>> {
(self.future_fn)(shutdown_rx)
}
fn priority(&self) -> i32 {
self.priority
}
fn is_critical(&self) -> bool {
self.critical
}
}
#[cfg(test)]
mod tests {
use super::*;
use tokio::sync::oneshot;
#[tokio::test]
async fn test_spawn_task_new() {
let task = SpawnTask::new("test-task", async { Ok(()) });
assert_eq!(task.name(), "test-task");
assert!(task.dependencies().is_empty());
assert_eq!(task.priority(), 0);
assert!(!task.is_critical());
}
#[tokio::test]
async fn test_spawn_task_with_dependencies() {
let task = SpawnTask::new("test-task", async { Ok(()) })
.with_dependencies(vec!["dep-1".to_string(), "dep-2".to_string()]);
assert_eq!(task.dependencies(), vec!["dep-1", "dep-2"]);
}
#[tokio::test]
async fn test_spawn_task_with_priority() {
let task = SpawnTask::new("test-task", async { Ok(()) }).with_priority(10);
assert_eq!(task.priority(), 10);
}
#[tokio::test]
async fn test_spawn_task_with_critical() {
let task = SpawnTask::new("test-task", async { Ok(()) }).with_critical(true);
assert!(task.is_critical());
}
#[tokio::test]
async fn test_spawn_task_run() {
let task = SpawnTask::new("test-task", async { Ok(()) });
let (_tx, rx) = oneshot::channel();
let result = Box::new(task).run(rx).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_spawn_task_with_shutdown_run() {
let task = SpawnTask::with_shutdown("test-task", |shutdown_rx| {
async move {
tokio::time::timeout(std::time::Duration::from_millis(100), shutdown_rx)
.await
.ok();
Ok(())
}
});
let (tx, rx) = oneshot::channel();
tx.send(()).unwrap();
let result = Box::new(task).run(rx).await;
assert!(result.is_ok());
}
}