use std::sync::Arc;
#[async_trait::async_trait]
pub trait BackgroundTask: Send + Sync {
fn name(&self) -> &'static str;
async fn init(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
Ok(())
}
async fn run(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
}
pub type DynBackgroundTask = dyn BackgroundTask + Send + Sync;
pub fn background_task(task: impl BackgroundTask + 'static) -> Arc<DynBackgroundTask> {
Arc::new(task)
}
pub fn spawn_background_tasks(tasks: impl IntoIterator<Item = Arc<DynBackgroundTask>>) {
for task in tasks {
let _ = actix_web::rt::spawn(async move {
if let Err(err) = task.init().await {
tracing::error!(name = task.name(), error = %err, "background task init failed");
return;
}
if let Err(err) = task.run().await {
tracing::error!(name = task.name(), error = %err, "background task failed");
}
});
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
struct FlagTask {
flag: Arc<AtomicBool>,
}
#[async_trait::async_trait]
impl BackgroundTask for FlagTask {
fn name(&self) -> &'static str {
"flag"
}
async fn run(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
self.flag.store(true, Ordering::SeqCst);
Ok(())
}
}
#[actix_web::test]
async fn run_is_callable() {
let task = FlagTask {
flag: Arc::new(AtomicBool::new(false)),
};
task.run().await.unwrap();
assert!(task.flag.load(Ordering::SeqCst));
}
#[actix_web::test]
async fn spawn_skips_run_when_init_fails() {
struct Boom;
#[async_trait::async_trait]
impl BackgroundTask for Boom {
fn name(&self) -> &'static str {
"boom"
}
async fn init(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
Err("nope".into())
}
async fn run(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
panic!("run must not run");
}
}
spawn_background_tasks([background_task(Boom)]);
actix_web::rt::time::sleep(Duration::from_millis(50)).await;
}
struct Ordered {
init: Arc<AtomicBool>,
run: Arc<AtomicBool>,
}
#[async_trait::async_trait]
impl BackgroundTask for Ordered {
fn name(&self) -> &'static str {
"ordered"
}
async fn init(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
self.init.store(true, Ordering::SeqCst);
Ok(())
}
async fn run(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
assert!(self.init.load(Ordering::SeqCst));
self.run.store(true, Ordering::SeqCst);
Ok(())
}
}
#[actix_web::test]
async fn spawn_inits_then_runs() {
let init = Arc::new(AtomicBool::new(false));
let run = Arc::new(AtomicBool::new(false));
spawn_background_tasks([background_task(Ordered {
init: Arc::clone(&init),
run: Arc::clone(&run),
})]);
actix_web::rt::time::sleep(Duration::from_millis(50)).await;
assert!(init.load(Ordering::SeqCst));
assert!(run.load(Ordering::SeqCst));
}
}