Skip to main content

scheduler/
lib.rs

1//! Async scheduling for a single logical job at a time.
2//!
3//! The scheduler decides when to trigger work and persists the resulting job
4//! state through a [`StateStore`]. Domain-specific retry, idempotency, and
5//! cursor management remain in the caller.
6//!
7//! Key semantics:
8//!
9//! - [`Schedule::AtTimes`] waits until each planned timestamp and treats an
10//!   empty list as a no-op schedule.
11//! - [`Schedule::Interval`] schedules the first run at `now + interval`.
12//! - [`Schedule::Cron`] evaluates a standard 5-field cron expression in
13//!   [`SchedulerConfig::timezone`].
14//! - [`Job::with_max_runs`] applies to every schedule kind; `0` exits without
15//!   running.
16//! - [`SchedulerConfig::timezone`] is forwarded through [`RunContext`], drives
17//!   [`Schedule::Cron`] evaluation, and does not rewrite absolute
18//!   [`Schedule::AtTimes`] values.
19//! - Restarts resume by `job_id` from the saved [`JobState::next_run_at`].
20//! - Dependency injection here means passing an explicit `deps` value when the
21//!   job is constructed. The scheduler does not auto-resolve parameters.
22//!
23//! ```rust
24//! use std::time::Duration;
25//!
26//! use scheduler::{InMemoryStateStore, Job, Schedule, Scheduler, SchedulerConfig, Task};
27//!
28//! let runtime = tokio::runtime::Runtime::new().unwrap();
29//! runtime.block_on(async {
30//!     let scheduler = Scheduler::new(SchedulerConfig::default(), InMemoryStateStore::new());
31//!     let job = Job::without_deps(
32//!         "doc-simple",
33//!         Schedule::Interval(Duration::from_millis(1)),
34//!         Task::from_async(|_| async { Ok(()) }),
35//!     )
36//!     .with_max_runs(1);
37//!
38//!     let report = scheduler.run(job).await.unwrap();
39//!     assert_eq!(report.history.len(), 1);
40//! });
41//! ```
42
43mod coordinated_store;
44mod error;
45mod execution_guard;
46mod guarded_runner;
47mod model;
48mod observer;
49mod scheduler;
50mod store;
51#[cfg(feature = "valkey-store")]
52mod valkey_coordinated_store;
53#[cfg(any(feature = "valkey-guard", feature = "valkey-store"))]
54mod valkey_execution_support;
55#[cfg(feature = "valkey-guard")]
56mod valkey_guard;
57#[cfg(feature = "valkey-store")]
58mod valkey_store;
59
60pub use coordinated_store::{
61    CoordinatedClaim, CoordinatedLeaseConfig, CoordinatedPendingTrigger, CoordinatedRuntimeState,
62    CoordinatedStateStore, NoopCoordinatedStateStore,
63};
64pub use error::{
65    ExecutionGuardError, ExecutionGuardErrorKind, InvalidJobError, InvalidJobKind, SchedulerError,
66    StoreError, StoreErrorKind, TaskJoinError, TaskJoinErrorKind,
67};
68pub use execution_guard::{
69    ExecutionGuard, ExecutionGuardAcquire, ExecutionGuardRenewal, ExecutionGuardScope,
70    ExecutionLease, ExecutionSlot, NoopExecutionGuard,
71};
72pub use guarded_runner::{GuardedRunResult, GuardedRunner};
73pub use model::{
74    CronSchedule, Job, JobFuture, JobResult, JobState, MissedRunPolicy, OverlapPolicy, RunContext,
75    RunRecord, RunStatus, Schedule, SchedulerConfig, SchedulerReport, Task, TaskContext,
76    TerminalStatePolicy,
77};
78pub use observer::{
79    LogObserver, NoopObserver, SchedulerEvent, SchedulerObserver, SchedulerStopReason,
80    StateLoadSource,
81};
82pub use scheduler::{Scheduler, SchedulerHandle};
83pub use store::{
84    InMemoryStateStore, ResilientStateStore, ResilientStoreError, StateStore, StoreEvent,
85    StoreOperation,
86};
87#[cfg(feature = "valkey-store")]
88pub use valkey_coordinated_store::ValkeyCoordinatedStateStore;
89#[cfg(feature = "valkey-guard")]
90pub use valkey_guard::{ValkeyExecutionGuard, ValkeyLeaseConfig};
91#[cfg(feature = "valkey-store")]
92pub use valkey_store::ValkeyStateStore;