✨ cron
This commit is contained in:
parent
18c663fcdb
commit
46cb21dc2a
8 changed files with 575 additions and 6 deletions
169
Cargo.lock
generated
169
Cargo.lock
generated
|
@ -26,6 +26,21 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android-tzdata"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android_system_properties"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.6.18"
|
version = "0.6.18"
|
||||||
|
@ -109,6 +124,12 @@ version = "2.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
|
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
|
@ -121,12 +142,35 @@ version = "1.10.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.2.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
|
||||||
|
dependencies = [
|
||||||
|
"shlex",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chrono"
|
||||||
|
version = "0.4.40"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
|
||||||
|
dependencies = [
|
||||||
|
"android-tzdata",
|
||||||
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
|
"num-traits",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorchoice"
|
name = "colorchoice"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
|
@ -147,6 +191,7 @@ dependencies = [
|
||||||
name = "comrade"
|
name = "comrade"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
"comrade-macro",
|
"comrade-macro",
|
||||||
"crossbeam",
|
"crossbeam",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
|
@ -172,6 +217,12 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation-sys"
|
||||||
|
version = "0.8.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam"
|
name = "crossbeam"
|
||||||
version = "0.8.4"
|
version = "0.8.4"
|
||||||
|
@ -315,6 +366,29 @@ version = "2.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone"
|
||||||
|
version = "0.1.61"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
|
||||||
|
dependencies = [
|
||||||
|
"android_system_properties",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"iana-time-zone-haiku",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone-haiku"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "icu_collections"
|
name = "icu_collections"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
|
@ -466,6 +540,16 @@ version = "1.0.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "js-sys"
|
||||||
|
version = "0.3.77"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.170"
|
version = "0.2.170"
|
||||||
|
@ -695,6 +779,12 @@ version = "0.1.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.20"
|
version = "1.0.20"
|
||||||
|
@ -745,6 +835,12 @@ version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d"
|
checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shlex"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.14.0"
|
version = "1.14.0"
|
||||||
|
@ -862,6 +958,79 @@ dependencies = [
|
||||||
"wit-bindgen-rt",
|
"wit-bindgen-rt",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.100"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
"rustversion",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-backend"
|
||||||
|
version = "0.2.100"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"log",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.100"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.100"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-backend",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.100"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-core"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-link"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
|
|
|
@ -16,3 +16,4 @@ rand = "0.9.0"
|
||||||
redis = "0.29.1"
|
redis = "0.29.1"
|
||||||
serde = "1.0.218"
|
serde = "1.0.218"
|
||||||
uuid = { version = "1.15.1", features = ["v4"] }
|
uuid = { version = "1.15.1", features = ["v4"] }
|
||||||
|
chrono = "0.4.40"
|
||||||
|
|
55
README.md
55
README.md
|
@ -28,7 +28,7 @@ let res: (i32, i32) = rally(items, |item: &i32| {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Run background tasks without blocking the main thread
|
// Run background tasks without blocking the main thread
|
||||||
background(|| {
|
background!(|| {
|
||||||
// Background task logic
|
// Background task logic
|
||||||
println!("This is a background task!");
|
println!("This is a background task!");
|
||||||
});
|
});
|
||||||
|
@ -52,6 +52,11 @@ let value: &str = retry(|| {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Delayed execution
|
||||||
|
delay(Duration::from_secs(4), || {
|
||||||
|
println!("I will run in 4 seconds from now on!");
|
||||||
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### Service Management
|
### Service Management
|
||||||
|
@ -74,6 +79,54 @@ fn run_services() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Cron Tasks
|
||||||
|
The ServiceManager also supports running functions periodically or on time:
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
let s = ServiceManager::new();
|
||||||
|
// Init Cron Manager
|
||||||
|
let cron = Cron::new();
|
||||||
|
|
||||||
|
// Add Cron Task
|
||||||
|
cron.add_task("4_sec", Schedule::Every(Duration::from_secs(4)), || {
|
||||||
|
println!("I run every 4 at {}", chrono::Utc::now());
|
||||||
|
});
|
||||||
|
|
||||||
|
cron.add_task("2_sec", Schedule::Every(Duration::from_secs(2)), || {
|
||||||
|
println!("I run every 2 seconds at {}", chrono::Utc::now());
|
||||||
|
});
|
||||||
|
|
||||||
|
cron.add_task(
|
||||||
|
"daily",
|
||||||
|
Schedule::Every(Duration::from_secs(60 * 60 * 24)),
|
||||||
|
|| {
|
||||||
|
println!("I run daily");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Start running the Cron Manager
|
||||||
|
let (s, cron) = s.register_cron(cron.into());
|
||||||
|
let s = s.spawn();
|
||||||
|
defer!(|| {
|
||||||
|
s.join().unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add another Cron Task after running the manager dynamically
|
||||||
|
cron.add_task(
|
||||||
|
"future_task",
|
||||||
|
Schedule::At(datetime_in(Duration::from_secs(2))),
|
||||||
|
|| {
|
||||||
|
println!("I am in the future");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Functionally the same as above
|
||||||
|
cron.run_at(datetime_in(Duration::from_secs(3)), || {
|
||||||
|
println!("The Future");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Worker Unions
|
### Worker Unions
|
||||||
|
|
||||||
You can annotate a function with `#[worker]` which gives them superpowers. These functions can be queued and dispatched by the system, and their results are returned when completed.
|
You can annotate a function with `#[worker]` which gives them superpowers. These functions can be queued and dispatched by the system, and their results are returned when completed.
|
||||||
|
|
59
examples/cron.rs
Normal file
59
examples/cron.rs
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
use comrade::{
|
||||||
|
cron::{Cron, Schedule},
|
||||||
|
datetime_in, defer, delay,
|
||||||
|
service::ServiceManager,
|
||||||
|
};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
|
let s = ServiceManager::new();
|
||||||
|
// Init Cron Manager
|
||||||
|
let cron = Cron::new();
|
||||||
|
|
||||||
|
// Add Cron Task
|
||||||
|
cron.add_task("4_sec", Schedule::Every(Duration::from_secs(4)), || {
|
||||||
|
println!("I run every 4 at {}", chrono::Utc::now());
|
||||||
|
});
|
||||||
|
|
||||||
|
cron.add_task("2_sec", Schedule::Every(Duration::from_secs(2)), || {
|
||||||
|
println!("I run every 2 seconds at {}", chrono::Utc::now());
|
||||||
|
});
|
||||||
|
|
||||||
|
cron.add_task(
|
||||||
|
"daily",
|
||||||
|
Schedule::Every(Duration::from_secs(60 * 60 * 24)),
|
||||||
|
|| {
|
||||||
|
println!("I run daily");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Start running the Cron Manager
|
||||||
|
let (s, cron) = s.register_cron(cron.into());
|
||||||
|
let s = s.spawn();
|
||||||
|
defer!(|| {
|
||||||
|
s.join().unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add another Cron Task after running the manager dynamically
|
||||||
|
cron.add_task(
|
||||||
|
"future_task",
|
||||||
|
Schedule::At(datetime_in(Duration::from_secs(2))),
|
||||||
|
|| {
|
||||||
|
println!("I am in the future");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Functionally the same as above
|
||||||
|
cron.run_at(datetime_in(Duration::from_secs(3)), || {
|
||||||
|
println!("The Future");
|
||||||
|
});
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
// Delayed execution
|
||||||
|
delay(Duration::from_secs(4), || {
|
||||||
|
println!("I will run in 4 seconds from now on!");
|
||||||
|
});
|
||||||
|
}
|
195
src/cron.rs
Normal file
195
src/cron.rs
Normal file
|
@ -0,0 +1,195 @@
|
||||||
|
use std::{
|
||||||
|
sync::{Arc, RwLock},
|
||||||
|
thread::JoinHandle,
|
||||||
|
time::Duration,
|
||||||
|
u64,
|
||||||
|
};
|
||||||
|
|
||||||
|
use chrono::Utc;
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
pub enum Schedule {
|
||||||
|
Every(Duration),
|
||||||
|
At(chrono::DateTime<Utc>),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CronTask {
|
||||||
|
f: Arc<Box<dyn Fn() + Send + Sync + 'static>>,
|
||||||
|
schedule: Schedule,
|
||||||
|
name: String,
|
||||||
|
last_run: Option<chrono::DateTime<Utc>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CronTask {
|
||||||
|
pub fn new<F: Fn() + Send + Sync + 'static>(name: &str, schedule: Schedule, f: F) -> Self {
|
||||||
|
Self {
|
||||||
|
f: Arc::new(Box::new(f)),
|
||||||
|
schedule,
|
||||||
|
name: name.to_string(),
|
||||||
|
last_run: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_absolute(&self) -> bool {
|
||||||
|
match self.schedule {
|
||||||
|
Schedule::Every(_) => false,
|
||||||
|
Schedule::At(_) => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&mut self) -> JoinHandle<()> {
|
||||||
|
log::info!("Starting cron task '{}'", self.name);
|
||||||
|
self.last_run = Some(Utc::now());
|
||||||
|
let f = Arc::clone(&self.f);
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
f.as_ref()();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wait_until(&mut self) -> Duration {
|
||||||
|
match self.schedule {
|
||||||
|
Schedule::Every(duration) => {
|
||||||
|
let now = Utc::now();
|
||||||
|
if let Some(last_exec) = self.last_run {
|
||||||
|
let since_then = (now - last_exec).to_std().unwrap();
|
||||||
|
|
||||||
|
duration.checked_sub(since_then).unwrap_or(Duration::ZERO)
|
||||||
|
} else {
|
||||||
|
self.last_run = Some(Utc::now());
|
||||||
|
duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Schedule::At(date_time) => {
|
||||||
|
if self.last_run.is_none() {
|
||||||
|
let now = Utc::now();
|
||||||
|
if let Ok(dur) = date_time.signed_duration_since(&now).to_std() {
|
||||||
|
dur
|
||||||
|
} else {
|
||||||
|
Duration::ZERO
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Duration::from_secs(u64::MAX)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Cron {
|
||||||
|
tasks: RwLock<Vec<CronTask>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cron {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
tasks: RwLock::new(Vec::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_task<F: Fn() + Send + Sync + 'static>(&self, name: &str, schedule: Schedule, f: F) {
|
||||||
|
self.tasks
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.push(CronTask::new(name, schedule, f));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_at<F: Fn() + Send + Sync + 'static>(&self, dt: chrono::DateTime<chrono::Utc>, f: F) {
|
||||||
|
let name = format!("delayed_{}", rand::rng().random_range(1000..9999));
|
||||||
|
self.tasks
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.push(CronTask::new(&name, Schedule::At(dt), f));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&self) {
|
||||||
|
loop {
|
||||||
|
// init
|
||||||
|
let mut last_wait = Duration::from_secs(u64::MAX);
|
||||||
|
let mut last_task: Option<usize> = None;
|
||||||
|
|
||||||
|
{
|
||||||
|
// find next task
|
||||||
|
let mut tasks = self.tasks.write().unwrap();
|
||||||
|
for (i, task) in tasks.iter_mut().enumerate() {
|
||||||
|
let wait_time = task.wait_until();
|
||||||
|
if wait_time < last_wait {
|
||||||
|
last_wait = wait_time;
|
||||||
|
last_task = Some(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(index) = last_task {
|
||||||
|
// init
|
||||||
|
let mut remove = false;
|
||||||
|
let mut skip = false;
|
||||||
|
|
||||||
|
// limit longest blocking time (5s)
|
||||||
|
let real_wait = if last_wait.gt(&Duration::from_secs(5)) {
|
||||||
|
skip = true;
|
||||||
|
Duration::from_secs(5)
|
||||||
|
} else {
|
||||||
|
last_wait
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
// logging
|
||||||
|
let tasks = self.tasks.read().unwrap();
|
||||||
|
log::debug!("Managing {} cron task(s)", tasks.len());
|
||||||
|
|
||||||
|
let task = tasks.get(index).unwrap();
|
||||||
|
if real_wait == last_wait {
|
||||||
|
log::debug!(
|
||||||
|
"Waiting for {real_wait:?} to start cron task '{}'",
|
||||||
|
task.name
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
log::debug!(
|
||||||
|
"Would wait for {last_wait:?} to start cron task '{}'. Waiting for {real_wait:?}",
|
||||||
|
task.name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if somehow we wait indefinitely
|
||||||
|
if last_wait == Duration::from_secs(u64::MAX) {
|
||||||
|
log::warn!("Infinite wait time for cron");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set remove flag for absolute time cron tasks
|
||||||
|
if task.is_absolute() {
|
||||||
|
log::info!(
|
||||||
|
"Removing task '{}' from cron because it will never run again",
|
||||||
|
task.name
|
||||||
|
);
|
||||||
|
remove = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sleep until task
|
||||||
|
std::thread::sleep(real_wait);
|
||||||
|
|
||||||
|
// skip if we are still just sleeping
|
||||||
|
if skip {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// run cron task
|
||||||
|
let mut tasks = self.tasks.write().unwrap();
|
||||||
|
let task = tasks.get_mut(index).unwrap();
|
||||||
|
let _ = task.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
if remove {
|
||||||
|
{
|
||||||
|
// remove if requested
|
||||||
|
let mut tasks = self.tasks.write().unwrap();
|
||||||
|
log::info!("Removing cron task #{index}");
|
||||||
|
tasks.remove(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
src/defer.rs
14
src/defer.rs
|
@ -1,16 +1,22 @@
|
||||||
|
use std::mem::take;
|
||||||
|
|
||||||
pub struct Defer {
|
pub struct Defer {
|
||||||
f: Box<dyn Fn()>,
|
f: Option<Box<dyn FnOnce()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Defer {
|
impl Defer {
|
||||||
pub fn new<F: Fn() + 'static>(f: F) -> Self {
|
pub fn new<F: FnOnce() + 'static>(f: F) -> Self {
|
||||||
Self { f: Box::new(f) }
|
Self {
|
||||||
|
f: Some(Box::new(f)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Defer {
|
impl Drop for Defer {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
log::debug!("Calling defer function");
|
log::debug!("Calling defer function");
|
||||||
self.f.as_ref()();
|
if let Some(f) = take(&mut self.f) {
|
||||||
|
f();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
76
src/lib.rs
76
src/lib.rs
|
@ -1,5 +1,11 @@
|
||||||
use std::{sync::mpsc, thread, time::Instant};
|
#![feature(fn_traits)]
|
||||||
|
use std::{
|
||||||
|
sync::mpsc,
|
||||||
|
thread,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod cron;
|
||||||
mod defer;
|
mod defer;
|
||||||
pub mod iterated;
|
pub mod iterated;
|
||||||
pub mod job;
|
pub mod job;
|
||||||
|
@ -69,3 +75,71 @@ pub fn retry<O, F: Fn() -> Option<O>>(f: F) -> O {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Run a background task.
|
||||||
|
///
|
||||||
|
/// This spawns a seperate thread for a background process.
|
||||||
|
/// The background task is guaranteed to finish within its defined scope.
|
||||||
|
/// If the end of the scope is reached while the thread is still running it will block.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```ignore
|
||||||
|
/// use comrade::background;
|
||||||
|
///
|
||||||
|
/// fn do_work() {
|
||||||
|
/// println!("doing work...");
|
||||||
|
///
|
||||||
|
/// // spawn background thread
|
||||||
|
/// background!(|| {
|
||||||
|
/// println!("doing something in the background");
|
||||||
|
/// std::thread::sleep(std::time::Duration::from_secs(3));
|
||||||
|
/// });
|
||||||
|
///
|
||||||
|
/// println!("doing something else...");
|
||||||
|
///
|
||||||
|
/// // end of scope
|
||||||
|
/// // the code will block until all background processes defined here are done.
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// do_work();
|
||||||
|
/// println!("finished with work");
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! background {
|
||||||
|
($f:expr) => {
|
||||||
|
let handle = std::thread::spawn(move || $f());
|
||||||
|
comrade::defer!(|| {
|
||||||
|
handle.join().unwrap();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start running a function after `duration`.
|
||||||
|
pub fn delay<F: Fn() + Send + 'static>(duration: std::time::Duration, f: F) {
|
||||||
|
let _ = std::thread::spawn(move || {
|
||||||
|
log::info!("Will start running in {duration:?}");
|
||||||
|
std::thread::sleep(duration);
|
||||||
|
f();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run `f(&T) -> X` for every item in `items`
|
||||||
|
pub fn parallel<T: Send + Sync + 'static, F, X: Send + 'static>(items: Vec<T>, f: F) -> Vec<X>
|
||||||
|
where
|
||||||
|
F: Fn(&T) -> X + Send + Sync + Copy + 'static,
|
||||||
|
{
|
||||||
|
let threads: Vec<_> = items
|
||||||
|
.into_iter()
|
||||||
|
.map(|x| std::thread::spawn(move || f(&x)))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
threads.into_iter().map(|x| x.join().unwrap()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn datetime_in(d: Duration) -> chrono::DateTime<chrono::Utc> {
|
||||||
|
chrono::Utc::now()
|
||||||
|
.checked_add_signed(chrono::TimeDelta::from_std(d).unwrap())
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,8 @@ use std::{
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::cron::Cron;
|
||||||
|
|
||||||
/// Status receiver of a dead man switch
|
/// Status receiver of a dead man switch
|
||||||
pub struct DeadManReceiver {
|
pub struct DeadManReceiver {
|
||||||
rx: Receiver<bool>,
|
rx: Receiver<bool>,
|
||||||
|
@ -83,6 +85,16 @@ impl ServiceManager {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn register_cron(self, cron: Arc<Cron>) -> (Self, Arc<Cron>) {
|
||||||
|
let cron_ret = Arc::clone(&cron);
|
||||||
|
(
|
||||||
|
self.register("cron", move |_| {
|
||||||
|
cron.run();
|
||||||
|
}),
|
||||||
|
cron_ret,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Register a new background service
|
/// Register a new background service
|
||||||
pub fn register<T: Fn(DeadManSwitch) -> () + 'static + Send + Sync>(
|
pub fn register<T: Fn(DeadManSwitch) -> () + 'static + Send + Sync>(
|
||||||
mut self,
|
mut self,
|
||||||
|
|
Loading…
Add table
Reference in a new issue