periodically/
lib.rs

1//! [Periodically][crate] provides a robust and ergonomic library for writing periodic or scheduled jobs in Rust.
2//!
3//! ### The Scheduler
4//!
5//! The [Scheduler] is the core of periodically. It allows for tasks to be registered and scheduled.
6//! It supports running synchronous [`Tasks`][Task] and asynchronous [`AsyncTasks`][AsyncTask] in the
7//! same scheduling runtime.
8//!
9//! The core design of the scheduler is based on the idea of decoupling an executable task from it's schedule.
10//!
11//! For example, consider how this naive example of a periodic job requires so much boilerplate:
12//!
13//! ```
14//! # use std::time::Duration;
15//! # use std::sync::{Arc, atomic::AtomicBool};
16//! # let should_terminate = true;
17//! std::thread::spawn(move || {
18//!   loop {
19//!     // ..
20//!     // do the actual work
21//!     // ..
22//!     if should_terminate {
23//!         break;
24//!     }
25//!     std::thread::sleep(Duration::from_millis(100));
26//!   }
27//! });
28//! ```
29//!
30//! This example also fails to account for edge cases like panic resilience.
31//!
32//! Whereas with `periodically`, the task can easily without worrying about the schedule,
33//!   and then the schedule can be injected later. This also enables the re-use of schedules
34//!   if there is common specialized logic for your application.
35//!
36//! ```
37//! # use periodically::Task;
38//! # use periodically::Scheduler;
39//! # use periodically::IntervalSchedule;
40//! # use std::time::Duration;
41//! # struct MyTask;
42//! # let runtime = tokio::runtime::Builder::new_current_thread().build().unwrap();
43//! impl Task for MyTask {
44//!   fn run(&self) {
45//!     // ..
46//!     // do the actual work
47//!     // ..
48//!   }
49//! }
50//! let mut scheduler = Scheduler::tokio_scheduler(runtime);
51//! scheduler.add_sync_task(MyTask {}, IntervalSchedule::every(Duration::from_secs(100)));
52//! ```
53//!
54//! [`Scheduler`] executes tasks in a serial fashion. aka; for every time a task is registered,
55//!   there is only ever at most one execution of that task.
56//!
57//! ### Tasks
58//!
59//! There are 2 types of task. The synchronous [`Task`], and it's async counterpart [`AsyncTask`].
60//!   Their interfaces are very similar, with the major difference being that [`AsyncTask`] returns a future that is [`Send`].
61//!
62//! Both types of task can be scheduled on the same [`Scheduler`], via [`add_sync_task`][Scheduler::add_sync_task]
63//!   and [`add_async_task`][Scheduler::add_async_task] respectively.
64//!
65//! ### Schedules
66//!
67//! The primary function of a [`Schedule`] is to consume context from a task execution, and decide when the next time that task will be executed.
68//!
69//! There are 3 control knobs of a [`Schedule`]:
70//! 1. [`initial`][Schedule::initial] - this is called the very first time a task is being scheduled for execution.
71//! 2. [`next`][Schedule::next] - this is called with the output of the last task execution.
72//! 3. [`next_on_task_panic`][Schedule::next_on_task_panic] - this is called when the last task execution panicked.
73//!
74//! By using these knobs, and the internal state of the  `impl Schedule`, there is a lot of flexibility in how dynamic schedulers can be built.
75//!   Additionally, since [`next`][Schedule::next] takes the output of the last task execution, an `impl Schedule` provides a way to egress
76//!   execution data via mechanims like mpsc channels if desired.
77//!
78//! ### Features
79//!
80//! By default, the only features enabled are `tokio` and `log`
81//!
82//! * `full`: Enables all features
83//! * `tokio`: Enables the tokio-based scheduler. As of 0.2.0, this is the only scheduler, and essentially required.
84//! * `log`: Enables an intergration with the [`log`] crate in the [`Scheduler`]. Helps provide debug information when dealing with problematic tasks.
85//! * `backoff`: Adds a built-in [`Schedule`] named [`BackoffSchedule`] which uses the external [`backoff`] crate.
86//! * `cron`: Adds a built-in [`Schedule`] named [`CronSchedule`] which uses the external [`cron`] crate.
87
88#![cfg_attr(docsrs, feature(doc_cfg))]
89
90/// Conditionally compiles a list of items under `feature_name`
91/// and annotates the docs of pub items with "Available on crate feature `feature_name` only.".
92///
93/// eg;
94///
95/// ```compile_fail
96/// cfg_feature! {
97///    "my-feature",
98///    pub mod my_feature;
99/// }
100/// ```
101///
102/// Transforms into
103///
104/// ```compile_fail
105/// # asdf
106/// #[cfg(feature = "my-feature")]
107/// #[cfg_attr(docsrs, doc(cfg(feature = "my-feature")))]
108/// pub mod my_feature;
109/// ```
110macro_rules! cfg_feature {
111    ($feature_name:literal, $($item:item)*) => {
112        $(
113            #[cfg(feature = $feature_name)]
114            #[cfg_attr(docsrs, doc(cfg(feature = $feature_name)))]
115            $item
116        )*
117    }
118}
119
120mod schedule;
121mod schedulers;
122
123use std::future::Future;
124
125pub use schedule::*;
126pub use schedulers::Scheduler;
127pub use schedulers::TaskIdentifier;
128
129/// Defines a task that can run in an synchronous runtime.
130pub trait Task<T = ()> {
131    /// Executes the task.
132    fn run(&self) -> T;
133}
134
135/// Defines a task that can run in an asynchronous runtime.
136pub trait AsyncTask<T = ()> {
137    /// Executes the task.
138    fn run(&self) -> impl Future<Output = T> + Send;
139}