Code Monkey home page Code Monkey logo

clokwerk's Introduction

Clokwerk, a simple scheduler

Crate API

Clokwerk is a simple scheduler, inspired by Python's Schedule and Ruby's clockwork. It uses a similar DSL for scheduling, rather than parsing cron strings.

By default, times and dates are relative to the local timezone, but the scheduler can be made to use a different timezone using the Scheduler::with_tz constructor.

Since version 0.4, Clokwerk has also supported a separate AsyncScheduler, which can easily run asynchronous tasks concurrently.

Usage

// Scheduler, and trait for .seconds(), .minutes(), etc.
use clokwerk::{Scheduler, TimeUnits};
// Import week days and WeekDay
use clokwerk::Interval::*;
use std::thread;
use std::time::Duration;

// Create a new scheduler
let mut scheduler = Scheduler::new();
// or a scheduler with a given timezone
let mut scheduler = Scheduler::with_tz(chrono::Utc);
// Add some tasks to it
scheduler.every(10.minutes()).plus(30.seconds()).run(|| println!("Periodic task"));
scheduler.every(1.day()).at("3:20 pm").run(|| println!("Daily task"));
scheduler.every(Tuesday).at("14:20:17").and_every(Thursday).at("15:00").run(|| println!("Biweekly task"));

// Manually run the scheduler in an event loop
for _ in 1..10 {
    scheduler.run_pending();
    thread::sleep(Duration::from_millis(10));
}
// Or run it in a background thread
let thread_handle = scheduler.watch_thread(Duration::from_millis(100));
// The scheduler stops when `thread_handle` is dropped, or `stop` is called
thread_handle.stop();

See documentation for additional examples of usage.

Similar libraries

clokwerk's People

Contributors

brokenthorn avatar io12 avatar mdsherry avatar msrd0 avatar tottoto avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

clokwerk's Issues

Run Once Scheduling Missing

Firstly, thanks for the library.

It would be great to have a run once job supported i.e. run this task at 6:30 am on 2nd October 2019.

Suggestion:

scheduler.on("02-10-2019")
    .by("14:20:17")
    // don't reschedule if `on` is called.
    .run(|| println!("Ran on 2nd October, 2019 by 14:20:17")); 

or

scheduler.on("02-10-2019")
    .by("14:20:17")
    .run_once(|| println!("Ran on 2nd October, 2019 by 14:20:17"));

Will it trigger the next round of job if the last one has not completed?

When we use something like scheduler.every(1.seconds()).run(move || {}), if the job is heavy and can't complete at 1 second, when the next second comes, the last job has not finished, will it trigger a new round executing? (As I have tested, then answer looks like no). Thank you for official answer!

Cargo audit error, RUSTSEC-2020-0071

Hello!
It appears cargo audit is failing because of the use of chrono, which uses time 0.1.45 which has a potential segfault in it.
Would it be possible to remove the chrono dependency and use the time 0.4.0 crate instead?

Make daily task run today

Hello!

If I create a schedule to execute every day at, for example, 10:00, the first execution would be on the next day, even if the current time is less than 10:00:

scheduler
    .every(1.day())
        .at("10:00")
    .run(|| println!("Daily task"));

I am expecting this code to create a task that would run today at 10:00 if the current time is less than that.
Is this possible?

feat: Add an API that allows immediate execution first then tick the timer

In my case, after setting every(1.minutes()), it always starts after 1 minute of execution.
If I setting every(10.minutes()), it starts after 10 minutes.

Maybe you can consider adding an API that allows immediate execution first then tick the timer?

let mut scheduler = Scheduler::new();

scheduler
        .every(10.minutes())
        .run(|| {
            println!("execution");
        });

scheduler.exe_first(true);  //Like this?
//Or, this?
scheduler.immediate_run(true);

"Run every day at midnight" only runs every other day

I have code like so:

fn main() {
    env_logger::init();

    let mut scheduler = Scheduler::new();

    scheduler
        .every(1.days())
        .at("00:00")
        .run(do_the_thing);

    loop {
        scheduler.run_pending();
        thread::sleep(Duration::from_millis(200));
    }
}

From the logs generated by my application do_the_thing is only called once every other day at midnight. I would expect the function to be called every day at midnight.

do_the_thing is very lightweight; it pretty much just sends out 3 HTTP POST requests, and does no time-consuming processing.

Is this a bug, or expected behavior? If this is expected behavior, how do I set up the scheduler to achieve what I want? Thanks!

Inconsistent scheduling with custom scheduler thread

I'm trying to have a somewhat accurate yet flexible scheduler (it seems like I'd need to replace the scheduler every time I update it), so I came up with some test code like this:

lazy_static! {
        static ref SCHEDULER: Mutex<Scheduler> = Mutex::new(Scheduler::new());
}

fn init_scheduler() {
        let mut sched = SCHEDULER.try_lock().expect("Mutex was locked after initialization");
        sched.every(5.seconds()).run(|| info!("Hello World"));
}

fn main() {
        pretty_env_logger::init_timed();

        init_scheduler();

        info!("Scheduler started");
        loop {
                let mut nanos = 1_100_000_000 - (Local::now().nanosecond() as u64);
                thread::sleep(Duration::from_nanos(nanos));
                match SCHEDULER.try_lock() {
                        Ok(mut sched) => sched.run_pending(),
                        Err(err) => warn!("Unable to run scheduler: {}", err)
                };
        }
}

I explicitly decided to align the scheduler calls to every second because even with just calling a logger, I got varying time intervals when just sleeping a second instead. However, this code made it absolutely worse as I now get 4, 5 and 6 second intervals randomly instead of a clean 5 second interval that I expected.

Since your scheduler seems to align everything to the next minute/hour/day, I'm supprised by this behaviour. Now, one second offset is not very bad, but I was hoping that for a schedule every 10 minutes and one every half our, I'd get away with calling the scheduler every 10 minutes, but having the interval fluctuate to 20 minutes is not acceptable.

Not Compatible Change to Version 0.3.2

I have had compile errors on codes which work well in 0.3.0 while it is in 0.3.2

The first one:

let time = WORKING_TIMEZONE
			.ymd(2020, 1, 1)
			.and_hms(WORKING_DAY_END_HOUR, WORKING_DAY_END_MIN, 0)
			.with_timezone(&Utc)
			.time();

		schedule
			.every(Interval::Weekday)
			.at(&format!("{}:{}", time.hour(), time.minute()))
			.run(|| (&*GONG_THREAD_NOTIFIER).send(()).report_err());

and the error message:

.at(&format!("{}:{}", time.hour(), time.minute()))

   |              ^^ the trait `std::convert::From<&std::string::String>` is not implemented for `clokwerk::intervals::ClokwerkTime`

   |

   = note: required because of the requirements on the impl of `std::convert::Into<clokwerk::intervals::ClokwerkTime>` for `&std::string::String`

   = note: required because of the requirements on the impl of `std::convert::TryFrom<&std::string::String>` for `clokwerk::intervals::ClokwerkTime`

The second one:

let run_hours = [
			"06:00", "07:00", "08:00", "09:00", "10:00", "11:00", "12:00", "13:00", "14:00", "15:00", "16:00", "17:00",
			"18:00", "19:00", "20:00",
		];

		for run_hour in &run_hours {
			schedule.every(1.day()).at(run_hour).run(|| {

and the error message:

schedule.every(1.day()).at(run_hour).run(|| {

   |                                     ^^ the trait `std::convert::From<&&str>` is not implemented for `clokwerk::intervals::ClokwerkTime`

   |

   = note: required because of the requirements on the impl of `std::convert::Into<clokwerk::intervals::ClokwerkTime>` for `&&str`

   = note: required because of the requirements on the impl of `std::convert::TryFrom<&&str>` for `clokwerk::intervals::ClokwerkTime`

The version 0.3.2 should be yanked because it's not semver-compatible. And it could be released as 0.4.0

How to retry correctly in each round?

For example, I have a task like this:

fn task() {
   let ret: Result<(), Error> = may_fault_task();
   match ret {
      Ok() => (),
      Err(_) => {
        //I want retry this task,
        () 
      }
   };
}

///--------------- Scheduler and its handler
let mut scheduler = Scheduler::new();

    scheduler
        .every(1.minutes())
        .run(|| task() );

let handle_thread = scheduler.watch_thread(Duration::from_millis(100));

println!("Running scheduler...");

loop {
   thread::sleep(Duration::from_micros(100));
}

But I don't know how to retry correctly in task.


Should I call the task directly and recursively, or how?

If it times out during the retry (more than 1 minute and the next timer has been triggered), will it automatically skip the task or continue until some internal event triggers the shutdown?

trait NextTime is private

Hello,

first thanks a lot for the crate ! I was wondering why the trait NextTime is private ?

Here's my use case for why I would like it to be public:

  • We have a library than spans a small server with some scheduled job scheduled with clokwerk.
  • We would like at every Interval to load all the events from the last 2 intervals. For that we need to provide a filter with a field start_date and would have liked to use:
use {
	clokwerk::{NextTime, Interval, TimeUnits},
	chrono::{DateTime, Utc},
};
// In reality load the interval from the servers' config
let my_interval = 5.minutes();

// When booting the server
let mut schedule = Scheduler::new();
schedule
	.every(my_interval)
	.run(move || {
		let now = Utc::now();

		let prev = my_interval.prev();

		let start_date = now - 2 * (now - prev);

		// Filter based on start_date ...
	});

If you accept to make NextTime public I can submit a PR :)

Have a nice day !

Document default timezone and add support for other timezones

Hi, thanks for a nice library!

I think it would make sense to document what time zone this package works with by default, and possibly also support to specify the timezone on either the job to run or the scheduler itself. Does this sound reasonable?

stop of thread_handle make hang

when you running :

thread_handle.stop();

before you don't passed duration time (xtime), since first time script below running :

scheduler.watch_thread(Duration::from_millis(xtime));
you will got hang process.

example:

//if you make duration time more greater, example on hour
let xtime=360000;
let thread_handle = scheduler.watch_thread(Duration::from_millis(xtime));
// will hang on step below
thread_handle.stop();
//no activity can't doing here...

DST change causes panic

Hi there,

I'm using a default Scheduler to trigger jobs at regular intervals, pretty standard, but when the timezone says it's time to move into winter time, aka when the clock moves back an hour, this happens:

thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', .cargo/registry/src/index.crates.io-6f17d22bba15001f/clokwerk-0.4.0/src/intervals.rs:214:41

And it will repeat this error on every restart for the next hour until the "repeated hour" has passed. After that, everything's back to working like normal.

For interval-only use cases I can likely work around this by settings the Scheduler's timezone to UTC, but that of course won't work for everyone.

Thanks for the otherwise great scheduler lib, btw.

Missing license

As the title says, this project is missing the license file.

What license would this project be licensed under?

Thanks :)

Multiple jobs on schedule with synchronous code leads to other jobs not kicking off + possibility of adding a "stop after" propety

I've got some code that looks like this:

    let mut scheduler = Scheduler::new();
    scheduler.every(Weekday).at("04:00").run(|| {
        for _ in 1..50 {
            cache_news();
            thread::sleep(Duration::from_secs(600));
        }
    });
    scheduler.every(Weekday).at("04:20").run(|| {
        for _ in 1..31 {
            cache_high_low();
            thread::sleep(Duration::from_secs(600));
        }
    });
    let _thread_handle = scheduler.watch_thread(Duration::from_millis(100));
    loop {
        thread::sleep(Duration::from_millis(1000));
    }

I've got the for loops in there because I essentially want to say "Run this every weekday starting at x time, run it y number of times, at a 10 minute interval (600 seconds)

Unless there is another way, I think it would be really cool if the API supported a stop_after(x) method which told it after 10 runs or 10 minutes, to stop so that I didn't have to do this.

The other issue that I had was because the code in the jobs are synchronous, it's blocking on the thread, so whatever one fires first, in this case, the top one, the bottom one never ends up starting when it should.

I could just create separate schedules for all of my jobs that spawn their own threads, but that would mean that the threads would stay open for all of my jobs for the entirety of the program running (which is all the time). I thought it might have been a little overkill. What I ended up coming up with was:

    let mut scheduler = Scheduler::new();
    scheduler.every(Weekday).at("04:00").run(|| {
        thread::spawn(|| {
            for _ in 1..50 {
                cache_news();
                thread::sleep(Duration::from_secs(600));
            }
        });
    });
    scheduler.every(Weekday).at("04:20").run(|| {
        thread::spawn(|| {
            for _ in 1..31 {
                cache_high_low();
                thread::sleep(Duration::from_secs(600));
            }
        });
    });
    let _thread_handle = scheduler.watch_thread(Duration::from_millis(100));
    loop {
        thread::sleep(Duration::from_millis(1000));
    }

So threads get spawned inside of the job where the work is being done in the event that the code in there is synchronous. I think it would be cool if there was an additional method to run() like run_as_own_thread() or something (less verbose) which would accomplish what I'm trying to do here.

If either of these suggestions are something of interest, I could try my best to do a pull request. But I'd also understand if you think that these issues are outside of the scope of the problem that this library is trying to solve. I do love the library, by the way!

Every day is broken

Hello,

I've just tried using your crate with this code:

    let mut scheduler = Scheduler::new();

    scheduler
        .every(1.day())
        .at("08:15")
        .and_every(1.day())
        .at("11:50")
        .and_every(1.day())
        .at("17:45")
        .run(move || {
            println!("Hello World");
    });

        thread::spawn(move || loop {
        scheduler.run_pending();
        thread::sleep(Duration::from_millis(500));
    });

And after some days the "Hello World" started being print like 10 times in a row at each specified time.

Don't print when a job is ran

Right now, when a job is executed, this is printed to my stdout for some reason:

Job { frequency: [RunConfig { base: Seconds(15), adjustment: None }], next_run: Some(2019-07-05T22:18:00.117662153+02:00), last_run: Some(2019-07-05T22:17:45.117662153+02:00), job: ??? }

This seems to be a leftover from debugging.

How do I pass argument to the function inside the schedule

The following is a mini version of what I am doing. I want to pass immutable structs to couple of schedules generated in a loop like

use std::sync::Arc;

use clokwerk::Interval::*;
use clokwerk::TimeUnits;
use std::time::Duration;
use std::thread;

#[derive(Clone, Debug)]
pub struct Person {
    pub name: String,
    pub age: u16,
}

pub fn print_debug_person(person_arc: Arc<Person>) {
    println!("Person is  :{:?}", person_arc)
}


fn main() {
    let p = Person { name: "Asnim".to_string(), age: 10 };

    let p = Arc::new(p);
    let mut scheduler = clokwerk::Scheduler::new();

    for loop_period in 1..5 {
        let person_arc = p.clone();
        scheduler.every(loop_period.seconds()).run(move || print_debug_person(person_arc));
    }

    loop {
        scheduler.run_pending();
        thread::sleep(Duration::from_millis(10));
    }
}

However the above code fails with compliation error

error[E0507]: cannot move out of `person_arc`, a captured variable in an `FnMut` closure
  --> src/main.rs:27:79
   |
26 |         let person_arc = p.clone();
   |             ---------- captured outer variable
27 |         scheduler.every(loop_period.seconds()).run(move || print_debug_person(person_arc));
   |                                                                               ^^^^^^^^^^ move occurs because `person_arc` has type `Arc<Person>`, which does not implement the `Copy` trait

What am I doing wrong here?

not able to use a `mpsc::channel` from a thread to a sched thread.

Hello,

I would like to create a channel between a scheduled task, and another thread.

Here is an example of what I'm trying to do:

extern crate clokwerk;

use clokwerk::TimeUnits;

fn main() {
    let (tx, rx) = std::sync::mpsc::channel();
    let mut scheduler = clokwerk::Scheduler::new();

    let th = std::thread::spawn(move || {
        let v = rx.recv().unwrap();

        assert!(v == 42);
    });

    let tx_clone = tx.clone();
    scheduler.every(30.seconds()).run(move || {
        tx_clone.send(42);
    });
}

and here is my compilation error:

error[E0277]: `std::sync::mpsc::Sender<i32>` cannot be shared between threads safely
  --> src/main.rs:16:35
   |
16 |     scheduler.every(30.seconds()).run(move || {
   |                                   ^^^ `std::sync::mpsc::Sender<i32>` cannot be shared between threads safely
   |
   = help: within `[closure@src/main.rs:16:39: 18:6 tx_clone:std::sync::mpsc::Sender<i32>]`, the trait `std::marker::Sync` is not implemented for `std::sync::mpsc::Sender<i32>`
   = note: required because it appears within the type `[closure@src/main.rs:16:39: 18:6 tx_clone:std::sync::mpsc::Sender<i32>]`

I'm not an rust expert, but I'm wondering why Job::run need std::marker::Sync tasks?
More over, I'm reading the std::thread::spawn documentation, and it needs only tasks std::marker::Send (https://doc.rust-lang.org/std/thread/fn.spawn.html).

Maybe I'm wrong, but I would like to know why Sync marker is necessary here, and could it be removed? Maybe do you have a explaination, and a rust trick to pass over this issue.

Regards,

update README

  • clokwerk = "0.4.0"
  • functions dosen't exist
.plus(30.seconds())
   |          ^^^^ method not found in `&mut SyncJob<_>`

.at("3:20 pm")
   |          ^^ method not found in `&mut SyncJob<_>`

Async Tasks

Hi, I am trying to use AsyncScheduler within my project and I was just wondering if there were any better or more ergonomic ways of doing this? I struggled a lot getting the state shared, but somehow got that working. Any tips or tricks greatly appreciated.

pub async fn scheduler_init(state: Arc<State>) {
    let mut scheduler = AsyncScheduler::new();

    // Refactor these blocks into add function
    let shared_state = Arc::clone(&state);
    scheduler.every(60.seconds()).run(move || {
        let shared_state = Arc::clone(&shared_state);
        async move {
            test_task(&shared_state).await
        }
    });
    // endblock

    // Refactor these blocks into add function
    let shared_state = Arc::clone(&state);
    scheduler.every(60.seconds()).run(move || {
        let shared_state = Arc::clone(&shared_state);
        async move {
            test_task_two(&shared_state).await
        }
    });
    // endblock
}

async fn test_task_two(state: &Arc<State>)  {
    // do something
}

async fn test_task(state: &Arc<State>) {
    // do something
}

Thank you

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.