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::StaggeredInterval`] spreads interval jobs by a stable phase
13//!   derived from the job id or an explicit seed.
14//! - [`Schedule::GroupedInterval`] spreads known group members evenly across
15//!   each interval.
16//! - [`Schedule::Cron`] evaluates a standard 5-field cron expression in
17//!   [`SchedulerConfig::timezone`].
18//! - [`JobTimeWindow`] can restrict execution by local weekday and time
19//!   segments; outside-window occurrences are skipped with
20//!   [`RunSkipReason::OutsideTimeWindow`].
21//! - [`Job::with_max_runs`] applies to every schedule kind; `0` exits without
22//!   running.
23//! - [`SchedulerConfig::timezone`] is forwarded through [`RunContext`], drives
24//!   [`Schedule::Cron`] evaluation, and does not rewrite absolute
25//!   [`Schedule::AtTimes`] values.
26//! - Restarts resume by `job_id` from the saved [`JobState::next_run_at`].
27//! - [`SchedulerHandle::pause`] pauses future scheduling without interrupting
28//!   the current run. [`SchedulerHandle::resume`] recomputes immediately and
29//!   applies the existing missed-run policy.
30//! - Pause scope is backend-specific: legacy schedulers pause locally, while
31//!   coordinated schedulers persist a shared pause state per `job_id`.
32//! - Dependency injection here means passing an explicit `deps` value when the
33//!   job is constructed. The scheduler does not auto-resolve parameters.
34//!
35//! ```rust
36//! use std::time::Duration;
37//!
38//! use scheduler::{InMemoryStateStore, Job, Schedule, Scheduler, SchedulerConfig, Task};
39//!
40//! let runtime = tokio::runtime::Runtime::new().unwrap();
41//! runtime.block_on(async {
42//!     let scheduler = Scheduler::new(SchedulerConfig::default(), InMemoryStateStore::new());
43//!     let job = Job::without_deps(
44//!         "doc-simple",
45//!         Schedule::Interval(Duration::from_millis(1)),
46//!         Task::from_async(|_| async { Ok(()) }),
47//!     )
48//!     .with_max_runs(1);
49//!
50//!     let report = scheduler.run(job).await.unwrap();
51//!     assert_eq!(report.history.len(), 1);
52//! });
53//! ```
54
55mod coordinated_store;
56mod error;
57mod execution_guard;
58mod guarded_runner;
59mod model;
60mod observer;
61mod scheduler;
62mod store;
63#[cfg(feature = "valkey-store")]
64mod valkey_coordinated_store;
65#[cfg(any(feature = "valkey-guard", feature = "valkey-store"))]
66mod valkey_execution_support;
67#[cfg(feature = "valkey-guard")]
68mod valkey_guard;
69#[cfg(any(feature = "valkey-guard", feature = "valkey-store"))]
70mod valkey_runtime;
71#[cfg(any(feature = "valkey-guard", feature = "valkey-store"))]
72mod valkey_scripts;
73#[cfg(feature = "valkey-store")]
74mod valkey_store;
75
76pub use coordinated_store::{
77    CoordinatedClaim, CoordinatedLeaseConfig, CoordinatedPendingTrigger, CoordinatedRuntimeState,
78    CoordinatedStateStore, NoopCoordinatedStateStore,
79};
80pub use error::{
81    ExecutionGuardError, ExecutionGuardErrorKind, InvalidJobError, InvalidJobKind, SchedulerError,
82    StoreError, StoreErrorKind, TaskJoinError, TaskJoinErrorKind,
83};
84pub use execution_guard::{
85    ExecutionGuard, ExecutionGuardAcquire, ExecutionGuardEvent, ExecutionGuardRenewal,
86    ExecutionGuardScope, ExecutionLease, ExecutionSlot, NoopExecutionGuard,
87};
88pub use guarded_runner::{GuardedRunResult, GuardedRunner};
89pub use model::{
90    CronSchedule, GroupedIntervalSchedule, Job, JobFuture, JobResult, JobState, JobTimeWindow,
91    MissedRunPolicy, OverlapPolicy, RunContext, RunRecord, RunSkipReason, RunStatus, Schedule,
92    SchedulerConfig, SchedulerReport, StaggeredIntervalSchedule, Task, TaskContext,
93    TerminalStatePolicy, TimeWindowSegment,
94};
95pub use observer::{
96    LogObserver, NoopObserver, PauseScope, SchedulerEvent, SchedulerObserver, SchedulerStopReason,
97    StateLoadSource,
98};
99pub use scheduler::{Scheduler, SchedulerHandle};
100pub use store::{
101    InMemoryStateStore, ResilientStateStore, ResilientStoreError, StateStore, StoreEvent,
102    StoreOperation,
103};
104#[cfg(feature = "valkey-store")]
105pub use valkey_coordinated_store::ValkeyCoordinatedStateStore;
106#[cfg(feature = "valkey-guard")]
107pub use valkey_guard::{ValkeyExecutionGuard, ValkeyLeaseConfig};
108#[cfg(any(feature = "valkey-guard", feature = "valkey-store"))]
109pub use valkey_runtime::ValkeyRecoveryConfig;
110#[cfg(feature = "valkey-store")]
111pub use valkey_store::ValkeyStateStore;