zslayton / cron

A cron expression parser in Rust
Apache License 2.0
376 stars 69 forks source link

feat: ScheduleIteratorBuf #104

Closed mmstick closed 2 years ago

mmstick commented 2 years ago

Requires #103 to be merged in advance.

This adds a new ScheduleIteratorBuf type with a static lifetime. Which also adds an Schedule::upcoming_buf and Schedule::after_buf. Tests were duplicated from ScheduleIterator, and the ScheduleIterator was improved to match the same implementation as Buf.

I need these changes to support a cron scheduling service for a distribution system updater service I'm working on. Lifetimes cause severe restrictions for use in an async context, which I would like to use to interrupt the scheduler as the scheduler receives new tasks to schedule, or is asked to remove a scheduled task.

mmstick commented 2 years ago

I have a working example using this branch for those interested. The design is not tied to any async runtime, and does not require "ticking" it at regular intervals. It uses this iterator to know precisely how long to wait for the next task, while job inserts and removals interrupt and reset the sleep.

use std::time::Duration;
use pop_task_scheduler::*;
use smol::Timer;
use chrono::offset::Local;

fn main() {
    smol::block_on(async move {
        // Creates a scheduler based on the Local timezone. Note that the `sched_service`
        // contains the background job as a future for the caller to decide how to await
        // it. When the scheduler is dropped, the scheduler service will exit as well.
        let (mut scheduler, sched_service) = Scheduler::<Local>::launch(Timer::after);

        // Creates a job which executes every 3 seconds.
        let job = Job::cron("1/3 * * * * *").unwrap();
        let fizz_id = scheduler.insert(job, |id| println!("Fizz"));

        // Creates a job which executes every 5 seconds.
        let job = Job::cron("1/5 * * * * *").unwrap();
        let buzz_id = scheduler.insert(job, |id| println!("Buzz"));

        // Creates a job which executes every 7 seconds.
        let job = Job::cron("1/7 * * * * *").unwrap();
        let bazz_id = scheduler.insert(job, |id| println!("Bazz"));

        // A future which gradually drops jobs from the scheduler.
        let dropper = async move {
            Timer::after(Duration::from_secs(7)).await;
            scheduler.remove(fizz_id);
            println!("Fizz gone");

            Timer::after(Duration::from_secs(5)).await;
            scheduler.remove(buzz_id);
            println!("Buzz gone");

            Timer::after(Duration::from_secs(1)).await;
            scheduler.remove(bazz_id);
            println!("Bazz gone");

            // `scheduler` is dropped here, which causes the sched_service to end.
        };

        // Poll the dropper and scheduler service concurrently until both return.
        futures::future::join(sched_service, dropper).await;
    });
}
zslayton commented 2 years ago

Hi, thanks for the PR! These changes look good. One superficial item: I'm not familiar with Buf as a convention to communicate owned rather than borrowed. How would you feel about OwnedScheduleIterator, upcoming_owned, etc?

mmstick commented 2 years ago

Sure. The convention was established with Path / PathBuf.

zslayton commented 2 years ago

Nice, thanks!

I need these changes to support a cron scheduling service for a distribution system updater service I'm working on.

Very cool. I'm a happy Pop!_OS user, it's neat to think I'll have some of my code running on my laptop.

zslayton commented 2 years ago

This change is now available in v0.11.0. It has also been published to crates.io.