#cron #timing #scale #async

schedules

A lightweight Rust library for managing operations across multiple time scales

4 releases (breaking)

Uses new Rust 2024

0.4.0 Mar 21, 2025
0.3.0 Mar 21, 2025
0.2.0 Mar 21, 2025
0.1.0 Mar 21, 2025

#180 in Concurrency

Download history 319/week @ 2025-03-19 3/week @ 2025-04-02

322 downloads per month

MIT license

1MB
1.5K SLoC

โฐ schedules.rs ๐Ÿš€

Schedules.rs Banner

โœจ A lightweight Rust library for scheduling operations across multiple time scales โœจ

๐Ÿ‘ฅ Who This Is For

This library is perfect for developers who need:

  • ๐ŸŽฏ Simple, predictable scheduling with absolute time intervals
  • ๐Ÿงฉ Lightweight scheduling without complex calendar-based rules
  • โš™๏ธ Precise control over execution timing in systems programming
  • ๐Ÿ”ง Efficient scheduling in resource-constrained environments

By design, we focus on absolute time durations (every 5 seconds, every 10 minutes) and deliberately avoid calendar-based scheduling (every Thursday, first day of month). This keeps the API clean, predictable, and easy to reason about for systems programming tasks.

๐ŸŽฏ Features

  • โšก Multiple tick frequencies (milliseconds to minutes)
  • ๐Ÿ•ฐ๏ธ Configurable time sources for ultimate flexibility
  • ๐Ÿ”„ Compound durations (e.g., 10 minutes plus 30 seconds)
  • ๐Ÿ“ Named schedules with intuitive fluent builder API
  • ๐Ÿ”— Direct callback references and event emission
  • ๐Ÿ’พ Easy serialization and state persistence
  • ๐Ÿ”„ Both synchronous AND asynchronous execution support
  • ๐Ÿ”€ Advanced scheduling patterns:
    • โฑ๏ธ Jittered intervals (add randomness to prevent thundering herds)
    • ๐Ÿ“ˆ Exponential backoff with configurable rate and maximum
    • ๐Ÿ“‰ Decay intervals that transition from quick to slow
    • ๐Ÿ”ข Limited execution counts (run exactly N times)
  • ๐ŸŽ›๏ธ Full configuration options for:
    • ๐Ÿงต Worker thread pool size
    • ๐Ÿงฎ Schedule store pre-allocation
    • ๐ŸŽฒ Deterministic operation with fixed RNG seeds

๐Ÿš€ Usage

// Create a scheduler with default settings
let mut scheduler = Scheduler::new();

// Or create with custom configuration
let config = SchedulerConfig {
    thread_count: 8,              // Use 8 worker threads
    store_capacity: 100,          // Pre-allocate for 100 schedules
    time_source: None,            // Use default time source
    rng_seed: Some(12345),        // Deterministic jitter with fixed seed
};
let mut scheduler = Scheduler::with_config(config);

// Register a callback handler (traditional way)
struct LogHandler;
impl CallbackHandler for LogHandler {
    fn handle(&self, event: TickEvent) {
        println!("Event fired: {:?}", event);
    }
    
    fn handle_async<'a>(&'a self, event: TickEvent) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
        Box::pin(async move { self.handle(event) })
    }
}

scheduler.register_callback("log_handler", Box::new(LogHandler))?;

// Or register a callback using the helper function (much simpler!)
scheduler.register_callback(
    "simple_logger", 
    callback_fn(|event| println!("Event: {}", event.schedule_name))
)?;

// Create a schedule with the registered handler
scheduler.every(Duration::from_secs(5))
    .with_name("status_check")
    .with_callback_id("log_handler")
    .build()?;

// For simple cases, use the convenience method
scheduler.every(Duration::from_millis(100))
    .with_name("fast_tick")
    .execute(|event| {
        println!("Fast tick: {:?}", event);
    })?;

// Create advanced schedule patterns
// Jittered schedule (adds randomness to prevent thundering herds)
scheduler.every(Duration::from_secs(2))
    .with_name("jittered_interval")
    .with_jitter(Duration::from_millis(500))
    .with_callback_id("log_handler")
    .build()?;

// Exponential backoff (good for retries)
scheduler.every(Duration::from_secs(1))
    .with_name("exponential_backoff")
    .exponential(2.0, Some(Duration::from_secs(8)))
    .with_callback_id("log_handler")
    .build()?;

// Decaying schedule (transitions from quick to slow intervals)
scheduler.every(Duration::from_millis(500))
    .with_name("decaying_interval")
    .decay_to(Duration::from_secs(3), Duration::from_secs(5))
    .with_callback_id("log_handler")
    .build()?;

// Limited schedule (only executes a set number of times)
scheduler.every(Duration::from_secs(1))
    .with_name("limited_executions")
    .max_executions(5)
    .with_callback_id("log_handler")
    .build()?;

// Start the scheduler
scheduler.start();

// You can manage schedules directly:
let all_schedules = scheduler.get_all_schedules();
scheduler.remove_schedule("some_id")?;
scheduler.clear_schedules();

// Later, freeze the state
let state = scheduler.freeze()?;
let serialized = serde_json::to_string(&state)?;

// Restore (requires re-registering handlers)
let state: SchedulerState = serde_json::from_str(&serialized)?;
let mut restored = Scheduler::restore(state)?;
restored.register_callback("log_handler", Box::new(LogHandler))?;
restored.start();

โšก Async Support

For asynchronous operation with Tokio (requires the async feature):

// Enable the feature in Cargo.toml:
// [dependencies]
// schedules = { version = "0.3.0", features = ["async"] }

// Create an async scheduler with default settings
let mut scheduler = AsyncScheduler::new();

// Or create with custom configuration
let config = SchedulerConfig {
    thread_count: 8,
    store_capacity: 100,
    time_source: None,
    rng_seed: Some(12345),  // For deterministic behavior
};
let mut scheduler = AsyncScheduler::with_config(config);

// Register an async handler
scheduler.register_async_handler("async_handler", |event| async move {
    println!("Async handling of event: {:?}", event);
    // Perform async operations here
    tokio::time::sleep(Duration::from_millis(100)).await;
})?;

// Create and start schedules as with the sync scheduler
scheduler.every(Duration::from_secs(1))
    .with_name("async_task")
    .with_callback_id("async_handler")
    .build()?;

// Create a schedule with an inline async closure
scheduler
    .every(Duration::from_secs(5))
    .with_name("inline_async")
    .execute(|event| async move {
        println!("Starting inline async task: {}", event.schedule_name);
        tokio::time::sleep(Duration::from_millis(1000)).await;
        println!("Completed inline async task: {}", event.schedule_name);
    })?;

scheduler.start();

๐Ÿ“ฆ Installation

Add to your Cargo.toml:

[dependencies]
schedules = "0.3.0"

# For async support:
schedules = { version = "0.3.0", features = ["async"] }

๐Ÿ“„ License

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

๐ŸŽ‰ Contribute

Contributions are welcome! Feel free to open issues and submit PRs to make schedule.rs even better!

Dependencies

~1โ€“10MB
~107K SLoC