qrush 2.0.1

Lightweight Job Queue and Task Scheduler for Rust (Actix + Redis + Cron)
Documentation

Qrush

Crates.io Documentation License Downloads

QRush

A lightweight, production-ready job queue and task scheduler for Rust applications built on Redis and Tokio. The core is web-framework agnostic, and the optional built-in dashboard works with either Actix Web or Axum. Qrush provides both integrated and separate process modes, making it suitable for everything from simple background tasks to large-scale distributed systems.

Features

  • πŸš€ Dual Deployment Modes: Integrated (single process) or separate worker process
  • 🧩 Framework Choice: Optional dashboard for Actix Web or Axum; the queue/worker core needs neither
  • ⚑ High Performance: Built on Redis and Tokio for maximum throughput
  • πŸ“… Cron Scheduling: Full cron expression support for recurring tasks
  • ⏱️ Delayed Jobs: Schedule jobs to run after a specified delay
  • πŸ“Š Built-in Metrics UI: Real-time dashboard for monitoring queues, jobs, and workers
  • πŸ”’ Security: Optional Basic Auth for metrics endpoints
  • 🎯 Type-Safe: Leverages Rust's type system for safe job handling
  • πŸ”„ Graceful Shutdown: Clean worker shutdown with configurable grace periods
  • πŸ“ˆ Scalable: Support for multiple queues with different priorities and concurrency levels

Feature Flags

The built-in dashboard is optional and works with either Actix or Axum β€” pick the one that matches your app.

Feature Default Description
dashboard-actix ❌ Metrics dashboard served with Actix Web (qrush::routes::metrics_route). Pulls in Actix Web, Tera, and the web stack.
dashboard-axum ❌ Metrics dashboard served with Axum (qrush::routes::axum_route). Pulls in Axum, Tera, and the web stack.
dashboard ❌ Back-compat alias for dashboard-actix.

Library-only usage (default). No dashboard framework is enabled by default, so a plain dependency gives you enqueue + workers with no web stack:

[dependencies]
qrush = "2.0.0"

To mount the dashboard, opt into one framework:

# Actix
qrush = { version = "2.0.0", features = ["dashboard-actix"] }

# Axum
qrush = { version = "2.0.0", features = ["dashboard-axum"] }

Migrating from 1.x to 2.0

In 1.x the dashboard was Actix-only and enabled by default. In 2.0 it is framework-selectable and off by default. Nothing else changed β€” the route-wiring function and all queue/worker/cron APIs are the same.

1.x 2.0
Dashboard default on (Actix) off
Enable Actix dashboard (default) features = ["dashboard-actix"]
Enable Axum dashboard not available features = ["dashboard-axum"]
# 1.x
qrush = "1.0.1"

# 2.0 β€” Actix (equivalent to the old default; no code changes needed)
qrush = { version = "2.0.0", features = ["dashboard-actix"] }

If you only used enqueue + workers (no dashboard), a plain qrush = "2.0.0" now pulls in less β€” the web stack is no longer compiled by default. See the CHANGELOG for the full list of changes.

Quick Start

Installation

Add to your Cargo.toml:

[dependencies]
qrush = "2.0.0"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
serde = { version = "1", features = ["derive"] }
async-trait = "0.1"
anyhow = "1"
futures = "0.3"

qrush bundles its own Redis client (with cluster support), so you don't need to depend on redis directly unless you use it yourself.

Basic Usage (Integrated Mode)

use qrush::job::Job;
use qrush::queue::{enqueue, enqueue_in};
use qrush::config::QueueConfig;
use qrush::registry::register_job;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use futures::future::BoxFuture;
use anyhow::Result;

#[derive(Clone, Serialize, Deserialize)]
pub struct EmailJob {
    pub to: String,
    pub subject: String,
}

#[async_trait]
impl Job for EmailJob {
    async fn perform(&self) -> Result<()> {
        println!("Sending email to {}: {}", self.to, self.subject);
        // Your email sending logic here
        Ok(())
    }

    fn name(&self) -> &'static str { "EmailJob" }
    fn queue(&self) -> &'static str { "default" }
}

impl EmailJob {
    pub fn name() -> &'static str { "EmailJob" }
    pub fn handler(payload: String) -> BoxFuture<'static, Result<Box<dyn Job>>> {
        Box::pin(async move {
            let job: EmailJob = serde_json::from_str(&payload)?;
            Ok(Box::new(job) as Box<dyn Job>)
        })
    }
}

#[tokio::main]
async fn main() -> Result<()> {
    // Set Redis URL
    std::env::set_var("REDIS_URL", "redis://127.0.0.1:6379");
    
    // Register job
    register_job(EmailJob::name(), EmailJob::handler);
    
    // Initialize queues
    let queues = vec![
        QueueConfig::new("default", 5, 0),
    ];
    QueueConfig::initialize(
        "redis://127.0.0.1:6379".to_string(),
        queues
    ).await?;
    
    // Enqueue a job
    enqueue(EmailJob {
        to: "user@example.com".to_string(),
        subject: "Hello!".to_string(),
    }).await?;
    
    // Keep running
    tokio::signal::ctrl_c().await?;
    Ok(())
}

Architecture

QRush supports two deployment modes:

Integrated Mode

Workers run in the same process as your application. Perfect for small to medium applications.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Application        β”‚
β”‚   (Single Process)   β”‚
β”‚                      β”‚
β”‚  β€’ HTTP Server       β”‚
β”‚  β€’ Enqueue Jobs      β”‚
β”‚  β€’ Process Jobs      β”‚ ← Workers here
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Separate Process Mode

Workers run in a dedicated process. Recommended for production environments.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Web Server         β”‚         β”‚  qrush-engine       β”‚
β”‚   (cargo run)        β”‚         β”‚  (separate process) β”‚
β”‚                      β”‚         β”‚                     β”‚
β”‚  β€’ HTTP Server       β”‚         β”‚  β€’ Worker Pools     β”‚
β”‚  β€’ Enqueue Jobs ─────┼──Redis──┼─▢ Process Jobs      β”‚
β”‚  β€’ Serve Routes      β”‚         β”‚  β€’ Cron Scheduler   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Documentation

Integrated Mode

See Part 1: Integrated Mode below for complete setup instructions.

Separate Process Mode

See Part 2: Separate Process Mode below for production deployment.

API Reference

Core Traits

  • Job: Implement this trait for your job types
  • CronJob: Implement for recurring scheduled jobs

Core Functions

  • enqueue(job) -> QrushResult<String>: Enqueue a job immediately; returns the job ID
  • enqueue_in(job, delay_secs) -> QrushResult<String>: Enqueue a job with a delay; returns the job ID
  • register_job(name, handler): Register a job handler
  • QueueConfig::initialize(redis_url, queues): Start worker pools and the cron scheduler

Cron Scheduling

All under qrush::cron::cron_scheduler::CronScheduler (see Cron Jobs):

  • register_cron_job(job) -> Result<()>: Persist a schedule to Redis
  • list_cron_jobs() -> Result<Vec<CronJobMeta>>: List registered cron jobs
  • run_now(cron_id) -> Result<String>: Enqueue a cron job immediately
  • toggle_cron_job(cron_id, enabled) -> Result<()>: Pause / resume a schedule
  • delete_cron_job(cron_id) -> Result<()>: Remove a schedule

Errors

The public API returns QrushResult<T> (Result<T, QrushError>). QrushError distinguishes Redis, Serialization, and Config failures, and implements std::error::Error, so it still propagates through ? in anyhow-based code.

Engine Runtime

  • qrush::engine::run_engine(redis_url, queues, shutdown_grace_secs): Run worker process
  • qrush::engine::parse_queues(spec): Parse queue specification string

Command-Line Interface

The crate also ships reference binaries β€” qrush (a management CLI with start/stop/status/stats/queues/jobs subcommands) and qrush-engine (the worker process) β€” that you can adapt for your own app. See src/bin/cli.md for the full CLI guide.

Examples

Runnable dashboard examples

The repo ships a complete, runnable dashboard example for each framework. With a Redis instance available (REDIS_URL, defaults to redis://127.0.0.1:6379):

# Actix β€” serves http://127.0.0.1:8080/qrush/metrics
cargo run --example actix_dashboard --features dashboard-actix

# Axum β€” serves http://127.0.0.1:8080/qrush/metrics
cargo run --example axum_dashboard --features dashboard-axum

Cron Jobs

A cron job is a regular Job that runs on a schedule instead of being enqueued by hand. The work still lives in Job::perform; CronJob only adds when to run it. Follow these three steps.

Step 1 β€” Define the job and its perform()

This is identical to any other QRush job: implement Job (the work + a handler so a worker can rebuild it from Redis).

use qrush::job::Job;
use qrush::registry::register_job;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use futures::future::BoxFuture;
use anyhow::Result;

#[derive(Clone, Serialize, Deserialize)]
pub struct EmailJob {
    pub to: String,
    pub subject: String,
}

#[async_trait]
impl Job for EmailJob {
    // πŸ‘‡ This is the execution β€” it runs every time the schedule fires.
    async fn perform(&self) -> Result<()> {
        println!("Sending email to {}: {}", self.to, self.subject);
        // Your recurring work goes here.
        Ok(())
    }

    fn name(&self) -> &'static str { "EmailJob" }
    fn queue(&self) -> &'static str { "default" }
}

impl EmailJob {
    pub fn name() -> &'static str { "EmailJob" }

    // Lets a worker rebuild the job from its stored payload.
    pub fn handler(payload: String) -> BoxFuture<'static, Result<Box<dyn Job>>> {
        Box::pin(async move {
            let job: EmailJob = serde_json::from_str(&payload)?;
            Ok(Box::new(job) as Box<dyn Job>)
        })
    }
}

Step 2 β€” Add the schedule (CronJob)

Attach a cron expression and a unique id to the same type.

use qrush::cron::cron_job::CronJob;

#[async_trait]
impl CronJob for EmailJob {
    // 6-field: sec min hour day month weekday. See "Cron Expressions" below.
    fn cron_expression(&self) -> &'static str { "0 0 * * * *" } // every hour
    fn cron_id(&self) -> &'static str { "hourly_email" }        // must be unique
}

Step 3 β€” Register and start it in main

The job above is framework-agnostic; only main differs. In both frameworks the order is the same:

  1. set_redis_url(...) β€” required before any Redis call.
  2. register_job(...) β€” so a worker can run the job.
  3. CronScheduler::register_cron_job(...) β€” saves the schedule to Redis.
  4. QueueConfig::initialize(...) β€” starts the workers and the cron scheduler.

⚠️ The cron scheduler only runs where QueueConfig::initialize is called. In Separate Process Mode that's the engine binary, not the web server β€” put steps 2–4 there.

Actix (features = ["dashboard-actix"]):

use qrush::config::{set_redis_url, QueueConfig};
use qrush::registry::register_job;
use qrush::cron::cron_scheduler::CronScheduler;
use qrush::routes::metrics_route::qrush_metrics_routes;
use actix_web::{web, App, HttpServer};

#[actix_web::main]
async fn main() -> anyhow::Result<()> {
    let redis_url = std::env::var("REDIS_URL")
        .unwrap_or_else(|_| "redis://127.0.0.1:6379".to_string());
    set_redis_url(redis_url.clone())?;                       // 1

    register_job(EmailJob::name(), EmailJob::handler);       // 2

    let job = EmailJob {                                     // 3
        to: "user@example.com".into(),
        subject: "Hourly report".into(),
    };
    CronScheduler::register_cron_job(job).await?;

    let queues = vec![QueueConfig::new("default", 5, 0)];    // 4
    QueueConfig::initialize(redis_url, queues).await?;

    // Serve the dashboard at http://127.0.0.1:8080/qrush/metrics
    HttpServer::new(|| {
        App::new().service(web::scope("/qrush").configure(qrush_metrics_routes))
    })
    .bind("0.0.0.0:8080")?
    .run()
    .await?;
    Ok(())
}

Axum (features = ["dashboard-axum"]):

use qrush::config::{set_redis_url, QueueConfig};
use qrush::registry::register_job;
use qrush::cron::cron_scheduler::CronScheduler;
use qrush::routes::axum_route::qrush_metrics_router;
use axum::Router;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let redis_url = std::env::var("REDIS_URL")
        .unwrap_or_else(|_| "redis://127.0.0.1:6379".to_string());
    set_redis_url(redis_url.clone())?;                       // 1

    register_job(EmailJob::name(), EmailJob::handler);       // 2

    let job = EmailJob {                                     // 3
        to: "user@example.com".into(),
        subject: "Hourly report".into(),
    };
    CronScheduler::register_cron_job(job).await?;

    let queues = vec![QueueConfig::new("default", 5, 0)];    // 4
    QueueConfig::initialize(redis_url, queues).await?;

    // Serve the dashboard at http://127.0.0.1:8080/qrush/metrics
    let app = Router::new().nest("/qrush", qrush_metrics_router());
    let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await?;
    axum::serve(listener, app).await?;
    Ok(())
}

That's it. When the schedule fires, QRush enqueues the job onto its queue() and a worker runs perform(). Watch it (and manage schedules) on the dashboard at /qrush/metrics/extras/cron.

On restart: register_cron_job errors if a job with the same cron_id already exists in Redis, so the ? above would abort a second boot. The schedule already survives restarts, so either skip re-registering, treat the duplicate as non-fatal (log and continue instead of ?), or call CronScheduler::delete_cron_job("hourly_email") first to re-seed it.

Managing cron jobs

Manage schedules from the dashboard at /qrush/metrics/extras/cron, or programmatically via CronScheduler:

use qrush::cron::cron_scheduler::CronScheduler;

CronScheduler::list_cron_jobs().await?;              // -> Vec<CronJobMeta>
CronScheduler::run_now("hourly_email").await?;       // enqueue once, right now
CronScheduler::toggle_cron_job("hourly_email", false).await?; // pause
CronScheduler::toggle_cron_job("hourly_email", true).await?;  // resume
CronScheduler::delete_cron_job("hourly_email").await?;        // remove entirely

To register a job that starts paused, override enabled() on the CronJob impl (it defaults to true); enable it later from the dashboard or with toggle_cron_job:

fn enabled(&self) -> bool { false }

A disabled job stays registered but is skipped and removed from the run schedule until re-enabled.

Multiple Queues

let queues = vec![
    QueueConfig::new("default", 5, 0),      // 5 workers, priority 0
    QueueConfig::new("critical", 10, 0),   // 10 workers, priority 0
    QueueConfig::new("low", 2, 1),         // 2 workers, priority 1
];

Metrics UI

Requires a dashboard feature β€” dashboard-actix or dashboard-axum (not enabled by default). See Feature Flags.

Access the built-in metrics dashboard at /qrush/metrics:

  • Queue statistics and job counts
  • Worker status and health
  • Cron job management
  • Job retry and deletion
  • CSV export

Requirements

  • Rust 1.89.0 or later
  • Redis 6.0 or later
  • Tokio runtime (multi-threaded)

Environment Variables

# Required
REDIS_URL=redis://127.0.0.1:6379

# Optional
QRUSH_BASIC_AUTH=admin:password  # Basic auth for metrics
RUST_LOG=info,qrush=info         # Logging level

Detailed Documentation

Integrated Mode (Detailed)

Use this mode when: You want a simple setup with workers running in the same process as your web server.

1. Add Dependencies

[dependencies]
# Pick the dashboard framework you use: "dashboard-actix" or "dashboard-axum"
qrush = { version = "2.0.0", features = ["dashboard-actix"] }
actix-web = "4"   # or: axum = "0.8"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
serde = { version = "1", features = ["derive"] }
async-trait = "0.1"
anyhow = "1"
futures = "0.3"

2. Define a Job

use qrush::job::Job;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use futures::future::BoxFuture;
use anyhow::Result;

#[derive(Clone, Serialize, Deserialize)]
pub struct NotifyUser {
    pub user_id: String,
    pub message: String,
}

#[async_trait]
impl Job for NotifyUser {
    async fn perform(&self) -> Result<()> {
        println!("Notify {} -> {}", self.user_id, self.message);
        Ok(())
    }

    fn name(&self) -> &'static str { "NotifyUser" }
    fn queue(&self) -> &'static str { "default" }
}

impl NotifyUser {
    pub fn name() -> &'static str { "NotifyUser" }
    pub fn handler(payload: String) -> BoxFuture<'static, Result<Box<dyn Job>>> {
        Box::pin(async move {
            let job: NotifyUser = serde_json::from_str(&payload)?;
            Ok(Box::new(job) as Box<dyn Job>)
        })
    }
}

3. Initialize QRush

The queue/worker setup is identical for both frameworks β€” only the dashboard wiring differs. The dashboard mounts at /qrush/metrics/... in both cases.

Actix (features = ["dashboard-actix"]):

use qrush::config::{QueueConfig, set_redis_url};
use qrush::registry::register_job;
use qrush::routes::metrics_route::qrush_metrics_routes;
use actix_web::{web, App, HttpServer};

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    // Set Redis URL
    let redis_url = std::env::var("REDIS_URL")
        .unwrap_or_else(|_| "redis://127.0.0.1:6379".to_string());
    set_redis_url(redis_url.clone())?;

    // Register jobs
    register_job(NotifyUser::name(), NotifyUser::handler);

    // Initialize queues
    let queues = vec![
        QueueConfig::new("default", 5, 0),
    ];
    QueueConfig::initialize(redis_url, queues).await?;

    // Start web server
    HttpServer::new(|| {
        App::new()
            .service(web::scope("/qrush").configure(qrush_metrics_routes))
    })
    .bind("0.0.0.0:8080")?
    .run()
    .await
}

Axum (features = ["dashboard-axum"]):

use qrush::config::{QueueConfig, set_redis_url};
use qrush::registry::register_job;
use qrush::routes::axum_route::qrush_metrics_router;
use axum::Router;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Set Redis URL
    let redis_url = std::env::var("REDIS_URL")
        .unwrap_or_else(|_| "redis://127.0.0.1:6379".to_string());
    set_redis_url(redis_url.clone())?;

    // Register jobs
    register_job(NotifyUser::name(), NotifyUser::handler);

    // Initialize queues
    let queues = vec![
        QueueConfig::new("default", 5, 0),
    ];
    QueueConfig::initialize(redis_url, queues).await?;

    // Mount the dashboard under /qrush
    let app = Router::new().nest("/qrush", qrush_metrics_router());

    let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await?;
    axum::serve(listener, app).await?;
    Ok(())
}

4. Enqueue Jobs

use qrush::queue::{enqueue, enqueue_in};

// Immediate
enqueue(NotifyUser {
    user_id: "123".to_string(),
    message: "Hello".to_string(),
}).await?;

// Delayed (300 seconds)
enqueue_in(NotifyUser {
    user_id: "123".to_string(),
    message: "Reminder".to_string(),
}, 300).await?;

Separate Process Mode (Detailed)

Use this mode when: You want production-ready separation with workers in a dedicated process.

1. Create Engine Binary

Create src/bin/qrush_engine.rs:

use qrush::engine::{run_engine, parse_queues};
use qrush::config::set_redis_url;
use qrush::registry::register_job;
use qrush::cron::cron_scheduler::CronScheduler;

// Your job types
use your_app::jobs::NotifyUser;

#[tokio::main(flavor = "multi_thread")]
async fn main() -> anyhow::Result<()> {
    dotenvy::dotenv().ok();
    
    // Setup logging
    tracing_subscriber::fmt::init();
    
    // Get Redis URL
    let redis_url = std::env::var("REDIS_URL")
        .unwrap_or_else(|_| "redis://127.0.0.1:6379".to_string());
    
    // Set Redis URL (needed before registering cron jobs)
    set_redis_url(redis_url.clone())?;
    
    // Register jobs
    register_job(NotifyUser::name(), NotifyUser::handler);
    
    // Register cron jobs (optional)
    let daily_report = DailyReportJob { /* ... */ };
    CronScheduler::register_cron_job(daily_report).await?;
    
    // Parse queues: "name:concurrency:priority"
    let queues = parse_queues("default:5:0,critical:10:0");
    
    // Run engine
    run_engine(redis_url, queues, 5).await
}

2. Update Cargo.toml

The engine binary above uses tracing_subscriber for logging and dotenvy to load .env, so add them alongside the [[bin]] entry:

[dependencies]
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
dotenvy = "0.15"

[[bin]]
name = "qrush_engine"
path = "src/bin/qrush_engine.rs"

3. Web Server (No Workers)

In your web server main.rs, only register jobs for enqueueing β€” do not call QueueConfig::initialize (that starts workers; the engine process owns them here). Use whichever framework you enabled. To also expose the dashboard, mount it exactly as shown in Integrated Mode β†’ Initialize QRush.

Actix (features = ["dashboard-actix"]):

use qrush::config::set_redis_url;
use qrush::registry::register_job;
use qrush::routes::metrics_route::qrush_metrics_routes;
use actix_web::{web, App, HttpServer};

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let redis_url = std::env::var("REDIS_URL")
        .unwrap_or_else(|_| "redis://127.0.0.1:6379".to_string());
    set_redis_url(redis_url).expect("failed to set Redis URL");

    // Register jobs (for serialization only β€” no worker initialization).
    register_job(NotifyUser::name(), NotifyUser::handler);

    HttpServer::new(|| {
        App::new().service(web::scope("/qrush").configure(qrush_metrics_routes))
    })
    .bind("0.0.0.0:8080")?
    .run()
    .await
}

Axum (features = ["dashboard-axum"]):

use qrush::config::set_redis_url;
use qrush::registry::register_job;
use qrush::routes::axum_route::qrush_metrics_router;
use axum::Router;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let redis_url = std::env::var("REDIS_URL")
        .unwrap_or_else(|_| "redis://127.0.0.1:6379".to_string());
    set_redis_url(redis_url)?;

    // Register jobs (for serialization only β€” no worker initialization).
    register_job(NotifyUser::name(), NotifyUser::handler);

    let app = Router::new().nest("/qrush", qrush_metrics_router());
    let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await?;
    axum::serve(listener, app).await?;
    Ok(())
}

4. Run Both Processes

Terminal 1 - Web Server:

export REDIS_URL=redis://127.0.0.1:6379
cargo run

Terminal 2 - Worker Engine:

export REDIS_URL=redis://127.0.0.1:6379
cargo run --bin qrush_engine

Cron Expressions

QRush accepts both 6-field (sec min hour day month weekday) and 5-field (min hour day month weekday) expressions. A 5-field expression defaults seconds to 0, so */5 * * * * and 0 */5 * * * * are equivalent.

Common examples:

Expression Meaning
"* * * * *" Every minute (5-field)
"0 * * * * *" Every minute (6-field)
"0 */5 * * * *" Every 5 minutes
"0 0 * * * *" Every hour
"0 0 0 * * *" Daily at midnight
"0 30 9 * * *" Daily at 09:30
"0 0 0 * * 1" Every Monday at midnight
"0 0 9 * * MON-FRI" Weekdays at 09:00
"0 0 0 1 * *" First day of every month
"0 0 12 1 JAN *" Jan 1st at noon

Each field supports the usual operators:

  • * β€” any value
  • a β€” an exact value
  • a,b,c β€” a list
  • a-b β€” an inclusive range
  • */n β€” a step over the whole range (e.g. */15 in minutes)
  • a-b/n β€” a step within a range
  • Names: months JAN–DEC, weekdays SUN–SAT (case-insensitive). For the weekday field, both 0 and 7 mean Sunday.

Timezone. Expressions evaluate in UTC by default. Override per job with fn timezone(&self) -> &'static str on the CronJob impl, returning any IANA name (e.g. "Asia/Kolkata", "America/New_York") β€” so "0 0 9 * * *" fires at 09:00 in that zone, DST included.

#[async_trait]
impl CronJob for EmailJob {
    fn cron_expression(&self) -> &'static str { "0 0 9 * * *" } // 9 AM…
    fn cron_id(&self) -> &'static str { "morning_email" }
    fn timezone(&self) -> &'static str { "Asia/Kolkata" }       // …IST
}

Precision & missed runs. The scheduler ticks every ~5 seconds, so a job fires within a few seconds of its scheduled time (don't rely on sub-5s precision). If the scheduler was down when a run was due, that run fires once on the next tick and is then re-anchored to its next future slot β€” missed cycles are not backfilled one-per-cycle. Claiming is atomic in Redis, so running multiple engine processes will not double-fire the same job.

Metrics Endpoints

  • GET /qrush/metrics - Dashboard overview
  • GET /qrush/metrics/queues/{queue} - Queue details
  • GET /qrush/metrics/extras/cron - Cron job management
  • GET /qrush/metrics/extras/workers - Worker status
  • GET /qrush/metrics/health - Health check
  • POST /qrush/metrics/jobs/action - Job actions (retry/delete)

Production Tips

  • Use separate process mode for production
  • Set QRUSH_BASIC_AUTH to protect metrics endpoints
  • Configure appropriate queue concurrency based on your workload
  • Monitor Redis memory usage
  • Use graceful shutdown for zero-downtime deployments
  • Scale workers horizontally by running multiple engine processes

License

This project is licensed under the MIT License - see the LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Support


Made with ❀️ by Srotas Space