From c1f59035933d1d1c62834cc2887e02a01b4bbaac Mon Sep 17 00:00:00 2001 From: kjuulh Date: Thu, 7 Aug 2025 11:14:55 +0200 Subject: [PATCH] feat: add readme Signed-off-by: kjuulh --- CHANGELOG.md | 2 +- Cargo.lock | 116 ++++++++++----------- README.md | 289 +++++++++++++++++++++++++++++++++++++++++++++++++++ cuddle.yaml | 6 +- 4 files changed, 346 insertions(+), 67 deletions(-) create mode 100644 README.md diff --git a/CHANGELOG.md b/CHANGELOG.md index d7b8b6d..99efb89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -92,6 +92,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - trigger commit - Merge pull request 'chore(release): v0.1.0' (#1) from cuddle-please/release into main -Reviewed-on: https://git.front.kjuulh.io/kjuulh/drift/pulls/1 +Reviewed-on: https://git.kjuulh.io/kjuulh/drift/pulls/1 - *(release)* 0.1.0 diff --git a/Cargo.lock b/Cargo.lock index 65e6974..544c184 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aho-corasick" @@ -60,9 +60,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" @@ -87,9 +87,9 @@ checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytes" @@ -99,18 +99,18 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.24" +version = "1.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16595d3be041c03b09d08d0858631facccee9221e579704070e6e9e4915d3bc7" +checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2" dependencies = [ "shlex", ] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "chrono" @@ -187,9 +187,9 @@ dependencies = [ [[package]] name = "io-uring" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" dependencies = [ "bitflags", "cfg-if", @@ -214,15 +214,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.172" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -245,15 +245,15 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] @@ -266,7 +266,7 @@ checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "wasi", - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -326,9 +326,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -336,9 +336,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", @@ -373,9 +373,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.12" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags", ] @@ -426,9 +426,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustversion" @@ -459,9 +459,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] @@ -474,25 +474,25 @@ checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.5.9" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] name = "syn" -version = "2.0.101" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -521,19 +521,18 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", - "once_cell", ] [[package]] name = "tokio" -version = "1.46.0" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1140bb80481756a8cbe10541f37433b459c5aa1e727b4c020fbfebdc25bf3ec4" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", @@ -546,7 +545,7 @@ dependencies = [ "slab", "socket2", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -562,9 +561,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", @@ -587,9 +586,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", @@ -598,9 +597,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", @@ -670,9 +669,9 @@ checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasm-bindgen" @@ -791,9 +790,9 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-result" @@ -813,15 +812,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-sys" version = "0.59.0" diff --git a/README.md b/README.md new file mode 100644 index 0000000..9f3fe00 --- /dev/null +++ b/README.md @@ -0,0 +1,289 @@ +# nodrift + +A lightweight Rust library for scheduling recurring jobs with automatic drift correction. When a job takes longer than expected, nodrift ensures the next execution adjusts accordingly to maintain consistent intervals. + +## Features + +- **Drift correction**: Automatically adjusts scheduling when jobs run longer than the interval +- **Async/await support**: Built on Tokio for efficient async job execution +- **Cancellation support**: Gracefully stop scheduled jobs using cancellation tokens +- **Custom job types**: Implement the `Drifter` trait for complex scheduling needs +- **Error handling**: Jobs can fail without crashing the scheduler +- **Detailed logging**: Built-in tracing support for debugging + +## Installation + +Add this to your `Cargo.toml`: + +```toml +[dependencies] +nodrift = "0.3.1" +tokio = { version = "1", features = ["full"] } +``` + +## Quick Start + +### Simple Function Scheduling + +The easiest way to schedule a recurring job is using the `schedule` function: + +```rust +use nodrift::{schedule, DriftError}; +use std::time::Duration; + +#[tokio::main] +async fn main() { + // Schedule a job to run every 5 seconds + let cancellation_token = schedule(Duration::from_secs(5), || async { + println!("Job executing!"); + + // Your job logic here + do_work().await?; + + Ok(()) + }); + + // Let it run for a while + tokio::time::sleep(Duration::from_secs(30)).await; + + // Cancel the job when done + cancellation_token.cancel(); +} + +async fn do_work() -> Result<(), DriftError> { + // Simulate some work + tokio::time::sleep(Duration::from_millis(100)).await; + Ok(()) +} +``` + +### Custom Drifter Implementation + +For more complex scheduling needs, implement the `Drifter` trait: + +```rust +use nodrift::{Drifter, schedule_drifter, DriftError}; +use async_trait::async_trait; +use tokio_util::sync::CancellationToken; +use std::time::Duration; + +#[derive(Clone)] +struct MyJob { + name: String, +} + +#[async_trait] +impl Drifter for MyJob { + async fn execute(&self, token: CancellationToken) -> anyhow::Result<()> { + println!("Executing job: {}", self.name); + + // Check for cancellation during long-running operations + if token.is_cancelled() { + return Ok(()); + } + + // Your job logic here + tokio::time::sleep(Duration::from_millis(100)).await; + + Ok(()) + } +} + +#[tokio::main] +async fn main() { + let job = MyJob { + name: "data-sync".to_string(), + }; + + // Schedule the custom job + let token = schedule_drifter(Duration::from_secs(10), job); + + // Run for a minute + tokio::time::sleep(Duration::from_secs(60)).await; + + // Stop the job + token.cancel(); +} +``` + +## How Drift Correction Works + +nodrift automatically handles timing drift that occurs when jobs take longer than expected: + +1. If a job is scheduled to run every 5 seconds but takes 3 seconds to complete, the next run will start 2 seconds after completion +2. If a job takes 7 seconds to complete (longer than the 5-second interval), the next run starts immediately after completion +3. This ensures your jobs maintain their intended frequency without overlapping executions + +## Error Handling + +Jobs that return errors will stop the scheduling routine: + +```rust +use nodrift::{schedule, DriftError}; +use std::time::Duration; + +let token = schedule(Duration::from_secs(1), || async { + // This will stop the scheduler after the first execution + Err(DriftError::JobError(anyhow::anyhow!("Something went wrong"))) +}); +``` + +## Logging + +nodrift uses the `tracing` crate for logging. Enable logging to see job execution details: + +```rust +use tracing_subscriber; + +fn main() { + // Initialize tracing + tracing_subscriber::fmt::init(); + + // Your application code +} +``` + +Log output includes: +- Job start times +- Execution duration +- Wait time until next run +- Next scheduled run time +- Any errors that occur + +## Examples + +### Database Cleanup Job + +```rust +use nodrift::{schedule, DriftError}; +use std::time::Duration; + +async fn cleanup_old_records() -> Result<(), DriftError> { + // Connect to database and clean up old records + println!("Cleaning up old records..."); + tokio::time::sleep(Duration::from_secs(1)).await; + Ok(()) +} + +#[tokio::main] +async fn main() { + // Run cleanup every hour + let token = schedule(Duration::from_secs(3600), || async { + cleanup_old_records().await + }); + + // Keep the application running + tokio::signal::ctrl_c().await.unwrap(); + + // Gracefully shutdown + token.cancel(); +} +``` + +### Metrics Collection + +```rust +use nodrift::{Drifter, schedule_drifter}; +use async_trait::async_trait; +use tokio_util::sync::CancellationToken; +use std::sync::Arc; +use std::sync::atomic::{AtomicU64, Ordering}; + +#[derive(Clone)] +struct MetricsCollector { + counter: Arc, +} + +#[async_trait] +impl Drifter for MetricsCollector { + async fn execute(&self, _token: CancellationToken) -> anyhow::Result<()> { + // Collect metrics + let value = self.counter.fetch_add(1, Ordering::Relaxed); + println!("Collected metric: {}", value); + + // Send to monitoring service + // send_to_monitoring(value).await?; + + Ok(()) + } +} + +#[tokio::main] +async fn main() { + let collector = MetricsCollector { + counter: Arc::new(AtomicU64::new(0)), + }; + + // Collect metrics every 30 seconds + let token = schedule_drifter( + Duration::from_secs(30), + collector + ); + + // Run indefinitely + tokio::signal::ctrl_c().await.unwrap(); + token.cancel(); +} +``` + +## API Reference + +### Functions + +#### `schedule(interval: Duration, func: F) -> CancellationToken` + +Schedules a function to run at the specified interval. + +- `interval`: Time between job executions +- `func`: Async function that returns `Result<(), DriftError>` +- Returns: `CancellationToken` to stop the scheduled job + +#### `schedule_drifter(interval: Duration, drifter: FDrifter) -> CancellationToken` + +Schedules a custom `Drifter` implementation. + +- `interval`: Time between job executions +- `drifter`: Implementation of the `Drifter` trait +- Returns: `CancellationToken` to stop the scheduled job + +### Traits + +#### `Drifter` + +```rust +#[async_trait] +pub trait Drifter { + async fn execute(&self, token: CancellationToken) -> anyhow::Result<()>; +} +``` + +Implement this trait for custom job types that need access to state or complex initialization. + +### Types + +#### `DriftError` + +Error type for job execution failures: + +```rust +pub enum DriftError { + JobError(#[source] anyhow::Error), +} +``` + +## Requirements + +- Rust 1.75 or later +- Tokio runtime + +## License + +This project is licensed under the MIT License. + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. + +## Support + +For issues and questions, please file an issue on the [GitHub repository](https://github.com/kjuulh/drift). diff --git a/cuddle.yaml b/cuddle.yaml index d0ad079..ca5b439 100644 --- a/cuddle.yaml +++ b/cuddle.yaml @@ -1,6 +1,6 @@ -# yaml-language-server: $schema=https://git.front.kjuulh.io/kjuulh/cuddle/raw/branch/main/schemas/base.json +# yaml-language-server: $schema=https://git.kjuulh.io/kjuulh/cuddle/raw/branch/main/schemas/base.json -base: "git@git.front.kjuulh.io:kjuulh/cuddle-rust-lib-plan.git" +base: "git@git.kjuulh.io:kjuulh/cuddle-rust-lib-plan.git" vars: service: "drift" @@ -12,6 +12,6 @@ please: repository: "drift" branch: main settings: - api_url: "https://git.front.kjuulh.io" + api_url: "https://git.kjuulh.io" actions: rust: