pub mod cron;
pub mod database;
pub mod fake;
pub mod job;
pub mod memory;
pub mod plugin;
pub mod queue;
pub mod schedule;
pub mod time;
pub mod worker;
#[cfg(test)]
mod tests_support;
pub use cron::{Cron, Weekday};
pub use database::{CreateQueueTables, DatabaseQueue};
pub use fake::{FakeQueue, fake};
pub use job::{
BoxFuture, DEFAULT_QUEUE, DEFAULT_RETRY_AFTER, DEFAULT_TRIES, FailedJob, Job, JobRegistry,
QueuedJob, ReservedJob,
};
pub use memory::MemoryQueue;
pub use plugin::QueueDashboard;
pub use queue::{Queue, QueueExt};
pub use schedule::{ScheduledEvent, Scheduler, TickReport, schedule};
pub use worker::{Outcome, Shutdown, Worker, WorkerOptions, WorkerStats, run_pool};
pub use rustlavel_core::{Error, Result};
pub mod prelude {
pub use crate::{
Cron, DatabaseQueue, FailedJob, FakeQueue, Job, JobRegistry, MemoryQueue, Queue, QueueExt,
QueuedJob, ReservedJob, Scheduler, Shutdown, Weekday, Worker, run_pool,
};
pub use rustlavel_core::{Error, Json, Result};
pub use std::future::Future;
pub use std::sync::Arc;
pub use std::time::Duration;
}
#[cfg(test)]
mod tests {
use crate::prelude::*;
use crate::tests_support::{CountingJob, FailingJob, counter, registry};
use crate::worker::Outcome;
#[tokio::test]
async fn a_job_travels_from_dispatch_through_a_worker_to_completion() {
let queue: Arc<dyn Queue> = Arc::new(MemoryQueue::new());
let worker = Worker::new(Arc::clone(&queue), Arc::new(registry()))
.poll_interval(Duration::from_millis(5));
let tally = counter();
queue.dispatch(&CountingJob::new(tally, 10)).await.unwrap();
queue.dispatch(&CountingJob::new(tally, 5)).await.unwrap();
queue.dispatch(&FailingJob::new(counter(), 1)).await.unwrap();
let shutdown = Shutdown::new();
let stats = worker.max_jobs(3).run(shutdown).await.unwrap();
assert_eq!(stats.processed, 2);
assert_eq!(stats.failed, 1);
assert_eq!(tally.total(), 15);
assert_eq!(queue.size("default").await.unwrap(), 0);
assert_eq!(queue.failed_jobs().await.unwrap().len(), 1);
}
#[tokio::test]
async fn the_fake_stands_in_for_the_real_thing_in_an_application_test() {
let queue = crate::fake();
let tally = counter();
queue.dispatch(&CountingJob::new(tally, 10)).await.unwrap();
queue.assert_pushed("counting").assert_pushed_times("counting", 1);
assert_eq!(tally.runs(), 0);
}
#[tokio::test]
async fn a_delayed_job_waits_and_a_worker_finds_nothing_meanwhile() {
let queue: Arc<dyn Queue> = Arc::new(MemoryQueue::new());
let worker = Worker::new(Arc::clone(&queue), Arc::new(registry()));
queue
.dispatch_later(Duration::from_secs(3600), &CountingJob::new(counter(), 1))
.await
.unwrap();
assert_eq!(worker.run_once().await.unwrap(), None);
assert_eq!(queue.size("default").await.unwrap(), 1);
}
#[tokio::test]
async fn the_scheduler_and_the_worker_meet_in_the_middle() {
let memory = Arc::new(MemoryQueue::new());
let queue: Arc<dyn Queue> = memory.clone();
let tally = counter();
let mut plan = Scheduler::new(Arc::clone(&queue));
plan.job(CountingJob::new(tally, 3)).daily().at("13:00");
plan.tick(crate::time::to_unix(2026, 8, 29, 13, 0, 0)).await.unwrap();
let worker = Worker::new(Arc::clone(&queue), Arc::new(registry()))
.poll_interval(Duration::from_millis(5))
.max_jobs(1);
let shutdown = Shutdown::new();
let signal = shutdown.clone();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_secs(5)).await;
signal.signal();
});
let stats = worker.run(shutdown).await.unwrap();
assert_eq!(stats.processed, 1);
assert_eq!(tally.total(), 3);
}
#[tokio::test]
async fn a_worker_survives_whatever_a_job_does_to_it() {
use crate::tests_support::PanickingJob;
let queue: Arc<dyn Queue> = Arc::new(MemoryQueue::new());
let worker = Worker::new(Arc::clone(&queue), Arc::new(registry()));
queue.dispatch(&PanickingJob::new(counter(), 1)).await.unwrap();
let healthy = counter();
queue.dispatch(&CountingJob::new(healthy, 1)).await.unwrap();
assert_eq!(worker.run_once().await.unwrap(), Some(Outcome::Failed));
assert_eq!(worker.run_once().await.unwrap(), Some(Outcome::Processed));
assert_eq!(healthy.runs(), 1);
}
}