run_every/lib.rs
1//! Run some code up to once per specified duration.
2//!
3//! See [macro documentation](run_every).
4
5use std::{
6 cell::Cell,
7 sync::Mutex,
8 thread::LocalKey,
9 time::{Duration, Instant},
10};
11
12#[doc(hidden)]
13pub fn __check(
14 thread_local_last: &'static LocalKey<Cell<Instant>>,
15 global_last: &Mutex<Instant>,
16 period: Duration,
17) -> bool {
18 let now = Instant::now();
19 let local_allow = thread_local_last.with(|last| {
20 let local_allow = now - last.get() > period;
21 if local_allow {
22 last.set(now);
23 }
24 local_allow
25 });
26 if local_allow {
27 let mut global_last = global_last.lock().unwrap();
28 let global_allow = now - *global_last > period;
29
30 if global_allow {
31 *global_last = now;
32 }
33 global_allow
34 } else {
35 false
36 }
37}
38
39/// Run some code up to once per specified duration.
40///
41/// Example:
42/// ```
43/// use run_every::run_every;
44/// # use std::time::Duration;
45/// # fn do_something() -> Result<(), ()> { Ok(()) }
46///
47/// for _ in 0..1000 {
48/// let result = do_something();
49/// if let Err(error) = result {
50/// run_every!(Duration::from_secs(10), println!("something failed: {error:?}"));
51/// }
52/// }
53/// ```
54///
55/// A block of code can also be used:
56/// ```
57/// # use run_every::run_every;
58/// # use std::time::Duration;
59/// run_every!(Duration::from_secs(10), {
60/// println!("error!");
61/// println!("retrying...");
62/// });
63/// ```
64///
65/// The implementation uses a combination of a global mutex and thread local storage
66/// to reduce contention.
67#[macro_export]
68macro_rules! run_every {
69 ($duration:expr, $stmt:stmt) => {{
70 use std::{
71 cell::Cell,
72 sync::{LazyLock, Mutex},
73 time::Instant,
74 };
75
76 thread_local! {
77 static __LAST: Cell<Instant> = Cell::new(Instant::now());
78 }
79 static __GLOBAL_LAST: LazyLock<Mutex<Instant>> =
80 LazyLock::new(|| Mutex::new(Instant::now()));
81 if ::run_every::__check(&__LAST, &__GLOBAL_LAST, $duration) {
82 $stmt
83 }
84 }};
85}
86
87#[doc(hidden)]
88pub const __SECOND: Duration = Duration::from_secs(1);
89
90/// Run some code up to once per second.
91///
92/// This is the same as <code>[run_every!](run_every)(Duration::from_secs(1), code)</code>.
93#[macro_export]
94macro_rules! run_every_second {
95 ($stmt:stmt) => {
96 ::run_every::run_every!(::run_every::__SECOND, $stmt)
97 };
98}