DocsOperations
Task scheduling
Dispatch registered background jobs on intervals, local or UTC daily times, and one-shot instants with explicit overlap policy.
Schedule jobs, not application logic
Start the job queue first, then build one scheduler for it during application startup. The target handler must already exist, so misspellings and invalid payloads fail while defining the schedule rather than at run time.
let scheduler = rahti::schedule::Scheduler::new(queue)
.every(
"refresh-search-index",
Duration::from_secs(5 * 60),
"refresh-index",
(),
)?
.daily(
"send-daily-summary",
8,
30,
"send-summary",
SummaryInput { locale: "en".into() },
)?
.once(
"finish-import",
SystemTime::now() + Duration::from_secs(60),
"finish-import",
ImportInput { import_id: 42 },
)?
.start();Timing forms
| Method | Behavior |
|---|---|
| every | Fixed delay; first run is one interval after startup. |
| daily | Calendar time in the installed APP_TIMEZONE. |
| daily_utc | Calendar time explicitly tied to UTC. |
| once | One SystemTime; a past instant runs promptly. |
| every_allowing_overlap | Opt-in concurrent occurrences for a safe handler. |
Overlap and backpressure
Recurring schedules prevent overlap by default while the prior job is queued, running, retrying, or backing off. A full queue skips the occurrence instead of waiting and accumulating an unbounded backlog. Missed interval ticks are coalesced; missed daily occurrences are not replayed after downtime.
Shutdown and observability
Shutdown stops timers before draining queues. Metrics count due, enqueued, overlap skips, capacity skips, and dispatch failures; diagnostics name the schedule and target job without logging its payload.
