foxtive-cron 0.4.0

Foxtive Cron
Documentation

🦊 Foxtive Cron

A lightweight, asynchronous, cron-based job scheduler for Rust powered by Tokio. Schedule and execute async tasks, including closures and blocking code, using standard cron expressions with per-second precision.

Features

  • Schedule jobs using standard 7-field cron expressions (with seconds)
  • Cron expressions are validated at registration time, no silent runtime failures
  • Supports both async and blocking closures out of the box
  • Fully extensible via the JobContract trait for custom job types
  • Lifecycle hooks: on_start, on_complete, on_error
  • Concurrent job execution, multiple jobs due at the same tick all fire together
  • Efficient scheduling via BinaryHeap (min-heap by next run time)
  • Built for the Tokio async runtime
  • Simple, ergonomic API

Installation

[dependencies]
foxtive-cron = "0.1"
tokio = { version = "1", features = ["full"] }

Usage

Async closure

use foxtive_cron::Cron;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let mut cron = Cron::new();

    // id, human-readable name, cron expression, async closure
    cron.add_job_fn("ping", "Ping", "*/5 * * * * * *", || async {
        println!("Ping at {}", chrono::Utc::now());
        Ok(())
    })?;

    tokio::spawn(async move { cron.run().await });

    tokio::signal::ctrl_c().await?;
    Ok(())
}

Blocking closure

cron.add_blocking_job_fn("backup", "Backup", "0 0 * * * * *", || {
    std::fs::write("/var/backup/snapshot", "data")?;
    Ok(())
})?;

The blocking function runs inside tokio::task::spawn_blocking so it never stalls the async runtime.

Custom Jobs via JobContract

For full control, implement JobContract directly on your own struct:

use foxtive_cron::{CronResult, Cron};
use foxtive_cron::contracts::{JobContract, ValidatedSchedule};
use async_trait::async_trait;
use std::borrow::Cow;
use std::sync::Arc;

struct MyJob {
    schedule: ValidatedSchedule,
}

impl MyJob {
    fn new() -> CronResult<Self> {
        Ok(Self {
            schedule: ValidatedSchedule::parse("0 * * * * * *")?,
        })
    }
}

#[async_trait]
impl JobContract for MyJob {
    fn id(&self) -> Cow<'_, str> { Cow::Borrowed("my-job") }
    fn name(&self) -> Cow<'_, str> { Cow::Borrowed("My Job") }
    fn schedule(&self) -> &ValidatedSchedule { &self.schedule }

    async fn run(&self) -> CronResult<()> {
        println!("Running my custom job at {}", chrono::Utc::now());
        Ok(())
    }

    // Optional lifecycle hooks (all default to no-ops):
    async fn on_start(&self) { println!("Starting..."); }
    async fn on_complete(&self) { println!("Done."); }
    async fn on_error(&self, err: &anyhow::Error) { eprintln!("Error: {err}"); }
}

#[tokio::main]
async fn main() -> CronResult<()> {
    let mut cron = Cron::new();
    cron.add_job(Arc::new(MyJob::new()?))?;

    tokio::spawn(async move { cron.run().await });

    tokio::signal::ctrl_c().await?;
    Ok(())
}

Cron Expression Format

Foxtive Cron uses a 7-field cron format:

sec  min  hour  day  month  weekday  year
Example Meaning
*/10 * * * * * * Every 10 seconds
0 * * * * * * Every minute
0 0 * * * * * Every hour
0 30 9 * * * * Every day at 09:30:00
0 0 0 1 * * * First day of every month

Expressions are validated via ValidatedSchedule::parse at registration time. An invalid expression returns an Err immediately rather than failing silently at runtime.

Lifecycle Hooks

Every job can optionally implement three hooks:

Hook Called when
on_start Just before run is invoked
on_complete After run returns Ok
on_error After run returns Err

All three default to no-ops, so you only implement what you need.

Thread Safety & Concurrency

  • Jobs are executed in independent tokio::spawn tasks — a slow job never blocks others.
  • Multiple jobs due at the same tick all fire concurrently in the same scheduler iteration.
  • Jobs are wrapped in Arc<dyn JobContract> and are required to be Send + Sync.

Logging

Job execution is traced via the tracing crate:

  • INFO on job start and successful completion
  • ERROR on job failure (the scheduler continues running)

Integrate with any tracing-compatible subscriber such as tracing-subscriber.

Roadmap

  • Graceful shutdown support
  • Pause / resume individual jobs
  • Remove a scheduled job by ID
  • Configurable retry logic on failure
  • Persistence / job state recovery

🙌 Contributing

Contributions, bug reports, and feature requests are welcome. Feel free to open issues or pull requests.

License

MIT License