# timer-lib
`timer-lib` is a Tokio-based timer crate for one-shot and recurring async work.
It is built around a small set of handle types:
- `Timer` starts, pauses, resumes, stops, cancels, and joins runs.
- `TimerBuilder` reduces setup boilerplate for common configurations.
- `TimerRegistry` tracks timers by ID and provides bulk operations.
- `TimerEvents` exposes lossy lifecycle broadcasts.
- `TimerCompletion` exposes lossless completed-run delivery.
## Features
- One-shot and recurring timers
- Deadline-based one-shot scheduling
- Optional initial delay for recurring timers
- Optional recurring jitter
- Pause, resume, graceful stop, and immediate cancel
- Dynamic interval adjustment for live runs
- Per-callback timeout support
- Retry policy and retry backoff support for failed callbacks
- Run outcomes and execution statistics
- Broadcast lifecycle events plus lossless completion waiting
- Labels, metadata tags, timer snapshots, and registry listing/filtering helpers
- Registry helpers for managing many timers, including bulk pause/resume
- Closure-first API with optional trait-based callbacks
- Optional `test-util` feature for deterministic mocked time
## Installation
```toml
[dependencies]
timer-lib = "0.4.0"
tokio = { version = "1", features = ["macros", "rt-multi-thread", "time"] }
```
## Quick Start
```rust
use std::time::Duration;
use timer_lib::{RecurringSchedule, Timer, TimerFinishReason};
#[tokio::main]
async fn main() {
let timer = Timer::new();
timer
.start_once(Duration::from_millis(250), || async {
println!("ran once");
Ok(())
})
.await
.unwrap();
let outcome = timer.join().await.unwrap();
assert_eq!(outcome.reason, TimerFinishReason::Completed);
}
```
## Recurring Timers
```rust
use std::time::Duration;
use timer_lib::{RecurringSchedule, Timer, TimerFinishReason};
#[tokio::main]
async fn main() {
let timer = Timer::recurring(
RecurringSchedule::new(Duration::from_secs(1)).with_expiration_count(3),
)
.start(|| async {
println!("tick");
Ok(())
})
.await
.unwrap();
let outcome = timer.join().await.unwrap();
assert_eq!(outcome.reason, TimerFinishReason::Completed);
assert_eq!(outcome.statistics.execution_count, 3);
}
```
## Retry And Timeout Controls
```rust
use std::time::Duration;
use timer_lib::{Timer, TimerError};
#[tokio::main]
async fn main() {
let timer = Timer::once(Duration::from_secs(1))
.callback_timeout(Duration::from_secs(2))
.max_retries(1)
.start(|| async {
// Do some async work here.
Ok::<(), TimerError>(())
})
.await
.unwrap();
let outcome = timer.join().await.unwrap();
println!("attempted: {}", outcome.statistics.execution_count);
}
```
## Deadlines, Jitter, And Backoff
```rust
use std::time::Duration;
use timer_lib::{RecurringSchedule, Timer, TimerError};
use tokio::time::Instant;
#[tokio::main]
async fn main() {
let deadline = Instant::now() + Duration::from_secs(1);
let timer = Timer::at(deadline)
.start(|| async { Ok::<(), TimerError>(()) })
.await
.unwrap();
let recurring = Timer::recurring(
RecurringSchedule::new(Duration::from_secs(5))
.with_jitter(Duration::from_secs(1))
.with_expiration_count(3),
)
.max_retries(2)
.exponential_backoff(Duration::from_millis(250))
.start(|| async { Ok::<(), TimerError>(()) })
.await
.unwrap();
let _ = timer.join().await.unwrap();
let _ = recurring.join().await.unwrap();
}
```
## Events And Completion
Use `subscribe()` when you want a best-effort event stream and `completion()` when you need to reliably observe the final outcome of a run.
```rust
use std::time::Duration;
use timer_lib::{Timer, TimerEvent};
#[tokio::main]
async fn main() {
let timer = Timer::new();
let mut events = timer.subscribe();
let mut completion = timer.completion();
let run_id = timer
.start_once(Duration::from_millis(100), || async { Ok(()) })
.await
.unwrap();
if let Some(TimerEvent::Started { run_id: seen, .. }) = events.wait_started().await {
assert_eq!(seen, run_id);
}
let outcome = completion.wait_for_run(run_id).await.unwrap();
println!("finished: {:?}", outcome.reason);
}
```
## Managing Many Timers
```rust
use std::time::Duration;
use timer_lib::TimerRegistry;
#[tokio::main]
async fn main() {
let registry = TimerRegistry::new();
let (timer_id, timer) = registry
.start_once(Duration::from_secs(1), || async { Ok(()) })
.await
.unwrap();
assert!(registry.contains(timer_id).await);
let _ = timer.join().await.unwrap();
let completed = registry.join_all().await;
assert!(!completed.is_empty());
}
```
## Labels And Snapshots
```rust
use std::time::Duration;
use timer_lib::Timer;
#[tokio::main]
async fn main() {
let timer = Timer::once(Duration::from_secs(1))
.label("billing")
.tag("tenant", "acme")
.start(|| async { Ok::<(), timer_lib::TimerError>(()) })
.await
.unwrap();
let snapshot = timer.snapshot().await;
assert_eq!(snapshot.metadata.label.as_deref(), Some("billing"));
}
```
## Callback Styles
The simplest API uses closures:
```rust
# use std::time::Duration;
# use timer_lib::Timer;
# async fn demo() {
let timer = Timer::new();
let _ = timer
.start_once(Duration::from_secs(1), || async { Ok(()) })
.await;
# }
```
If you need a reusable callback type, implement `TimerCallback`:
```rust
use async_trait::async_trait;
use timer_lib::{TimerCallback, TimerError};
struct MyCallback;
#[async_trait]
impl TimerCallback for MyCallback {
async fn execute(&self) -> Result<(), TimerError> {
Ok(())
}
}
```
## Current Scope
`timer-lib` currently targets Tokio runtimes. It does not provide cron scheduling or `async-std` support.
For deterministic test control, enable the `test-util` feature and use `Timer::new_mocked()` or `TimerRegistry::new_mocked()`.
The next major documentation pass should live on docs.rs. For now, the most complete usage sample is [examples/feature_showcase.rs](examples/feature_showcase.rs).