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//! - [`JobTimeWindow`] can restrict execution by local weekday and time
15//!   segments; outside-window occurrences are skipped with
16//!   [`RunSkipReason::OutsideTimeWindow`].
17//! - [`Job::with_max_runs`] applies to every schedule kind; `0` exits without
18//!   running.
19//! - [`SchedulerConfig::timezone`] is forwarded through [`RunContext`], drives
20//!   [`Schedule::Cron`] evaluation, and does not rewrite absolute
21//!   [`Schedule::AtTimes`] values.
22//! - Restarts resume by `job_id` from the saved [`JobState::next_run_at`].
23//! - Dependency injection here means passing an explicit `deps` value when the
24//!   job is constructed. The scheduler does not auto-resolve parameters.
25//!
26//! ```rust
27//! use std::time::Duration;
28//!
29//! use scheduler::{InMemoryStateStore, Job, Schedule, Scheduler, SchedulerConfig, Task};
30//!
31//! let runtime = tokio::runtime::Runtime::new().unwrap();
32//! runtime.block_on(async {
33//!     let scheduler = Scheduler::new(SchedulerConfig::default(), InMemoryStateStore::new());
34//!     let job = Job::without_deps(
35//!         "doc-simple",
36//!         Schedule::Interval(Duration::from_millis(1)),
37//!         Task::from_async(|_| async { Ok(()) }),
38//!     )
39//!     .with_max_runs(1);
40//!
41//!     let report = scheduler.run(job).await.unwrap();
42//!     assert_eq!(report.history.len(), 1);
43//! });
44//! ```
45
46mod coordinated_store;
47mod error;
48mod execution_guard;
49mod guarded_runner;
50mod model;
51mod observer;
52mod scheduler;
53mod store;
54#[cfg(feature = "valkey-store")]
55mod valkey_coordinated_store;
56#[cfg(any(feature = "valkey-guard", feature = "valkey-store"))]
57mod valkey_execution_support;
58#[cfg(feature = "valkey-guard")]
59mod valkey_guard;
60#[cfg(feature = "valkey-store")]
61mod valkey_store;
62
63pub use coordinated_store::{
64    CoordinatedClaim, CoordinatedLeaseConfig, CoordinatedPendingTrigger, CoordinatedRuntimeState,
65    CoordinatedStateStore, NoopCoordinatedStateStore,
66};
67pub use error::{
68    ExecutionGuardError, ExecutionGuardErrorKind, InvalidJobError, InvalidJobKind, SchedulerError,
69    StoreError, StoreErrorKind, TaskJoinError, TaskJoinErrorKind,
70};
71pub use execution_guard::{
72    ExecutionGuard, ExecutionGuardAcquire, ExecutionGuardRenewal, ExecutionGuardScope,
73    ExecutionLease, ExecutionSlot, NoopExecutionGuard,
74};
75pub use guarded_runner::{GuardedRunResult, GuardedRunner};
76pub use model::{
77    CronSchedule, Job, JobFuture, JobResult, JobState, JobTimeWindow, MissedRunPolicy,
78    OverlapPolicy, RunContext, RunRecord, RunSkipReason, RunStatus, Schedule, SchedulerConfig,
79    SchedulerReport, Task, TaskContext, TerminalStatePolicy, TimeWindowSegment,
80};
81pub use observer::{
82    LogObserver, NoopObserver, SchedulerEvent, SchedulerObserver, SchedulerStopReason,
83    StateLoadSource,
84};
85pub use scheduler::{Scheduler, SchedulerHandle};
86pub use store::{
87    InMemoryStateStore, ResilientStateStore, ResilientStoreError, StateStore, StoreEvent,
88    StoreOperation,
89};
90#[cfg(feature = "valkey-store")]
91pub use valkey_coordinated_store::ValkeyCoordinatedStateStore;
92#[cfg(feature = "valkey-guard")]
93pub use valkey_guard::{ValkeyExecutionGuard, ValkeyLeaseConfig};
94#[cfg(feature = "valkey-store")]
95pub use valkey_store::ValkeyStateStore;