use crate::state::AppState;
use async_trait::async_trait;
use std::sync::Arc;
#[async_trait]
pub trait ScheduledJob: Send + Sync {
fn name(&self) -> &str;
async fn run(&self, ctx: &AppState) -> anyhow::Result<()>;
}
pub struct Scheduler {
jobs: Vec<Arc<dyn ScheduledJob>>,
}
impl Default for Scheduler {
fn default() -> Self {
Self::new()
}
}
impl Scheduler {
pub fn new() -> Self {
Scheduler { jobs: Vec::new() }
}
pub fn register(&mut self, job: Arc<dyn ScheduledJob>) {
self.jobs.push(job);
}
pub fn jobs(&self) -> &[Arc<dyn ScheduledJob>] {
&self.jobs
}
pub async fn run_all_once(&self, ctx: &AppState) {
for job in &self.jobs {
if let Err(e) = job.run(ctx).await {
tracing::warn!(job = %job.name(), error = ?e, "scheduled job failed");
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use async_trait::async_trait;
use crate::registry::ExtensionRegistry;
use std::sync::atomic::{AtomicUsize, Ordering};
struct CountingJob {
counter: Arc<AtomicUsize>,
}
#[async_trait]
impl ScheduledJob for CountingJob {
fn name(&self) -> &str { "counter" }
async fn run(&self, _ctx: &AppState) -> anyhow::Result<()> {
self.counter.fetch_add(1, Ordering::SeqCst);
Ok(())
}
}
#[tokio::test]
async fn run_all_once_runs_all_jobs() {
let counter = Arc::new(AtomicUsize::new(0));
let mut s = Scheduler::new();
s.register(Arc::new(CountingJob { counter: counter.clone() }));
s.run_all_once(&test_app_state()).await;
assert_eq!(counter.load(Ordering::SeqCst), 1);
}
fn test_app_state() -> AppState {
let pool = sqlx::SqlitePool::connect_lazy(":memory:").unwrap();
AppState {
db: pool,
config: Arc::new(crate::config::Config::default()),
admin_token: None,
registry: Arc::new(ExtensionRegistry::new(vec![])),
wasm_loader: None,
site_override: Arc::new(tokio::sync::RwLock::new(None)),
builders: Arc::new(vec![]),
}
}
}