use std::path::PathBuf; use std::sync::Arc; use queue::{AppQueueSettings, Queue, QueueBuilder}; use tokio::signal; use tracing::{info, warn}; use crate::config::AppEmailSettings; use crate::error::EmailError; use crate::service::{EmailService, EmailServiceBuilder, EmailSubscribers}; #[derive(Debug, Default, Clone)] pub struct WorkerOptions { pub template_dir: Option, } pub struct RunningWorker { pub queue: Arc, pub service: EmailService, pub subscribers: EmailSubscribers, } pub async fn start_worker( email_cfg: AppEmailSettings, queue_cfg: AppQueueSettings, opts: WorkerOptions, ) -> Result { let queue = QueueBuilder::from_config(queue_cfg) .build() .await? .ok_or(EmailError::Config("queue disabled at startup".to_string()))?; let mut builder = EmailServiceBuilder::new(email_cfg, Arc::new(queue.clone())); if let Some(dir) = opts.template_dir { builder = builder.with_template_dir(dir); } let service = builder.build().await?.ok_or(EmailError::Config( "email service disabled at startup".to_string(), ))?; let subscribers = service.start_subscribers()?; info!("email worker subscribers started"); Ok(RunningWorker { queue: Arc::new(queue), service, subscribers, }) } pub async fn wait_for_shutdown() { let ctrl_c = async { if let Err(e) = signal::ctrl_c().await { warn!(error = %e, "failed to install ctrl-c handler"); } }; #[cfg(unix)] let term = async { match signal::unix::signal(signal::unix::SignalKind::terminate()) { Ok(mut s) => { s.recv().await; } Err(e) => { warn!(error = %e, "failed to install SIGTERM handler"); std::future::pending::<()>().await; } } }; #[cfg(not(unix))] let term = std::future::pending::<()>(); tokio::select! { _ = ctrl_c => info!("ctrl-c received, beginning shutdown"), _ = term => info!("SIGTERM received, beginning shutdown"), } } pub async fn run_until_shutdown( email_cfg: AppEmailSettings, queue_cfg: AppQueueSettings, opts: WorkerOptions, ) -> Result<(), EmailError> { let running = start_worker(email_cfg, queue_cfg, opts).await?; wait_for_shutdown().await; running.subscribers.shutdown().await?; running.service.shutdown().await?; info!("email worker shut down cleanly"); Ok(()) } #[cfg(test)] mod tests { use super::*; fn email_cfg() -> AppEmailSettings { AppEmailSettings { enabled: true, smtp_host: "smtp.example.com".into(), smtp_port: 587, ..Default::default() } } fn queue_cfg_disabled() -> AppQueueSettings { AppQueueSettings { enabled: false, ..Default::default() } } #[tokio::test] async fn start_worker_returns_error_when_queue_disabled() { let res = start_worker(email_cfg(), queue_cfg_disabled(), WorkerOptions::default()).await; assert!(matches!(res, Err(EmailError::Config(_)))); } }