Skip to main content

runledger_runtime/
lib.rs

1//! Async runtime loops for executing Runledger jobs against a persistence
2//! backend.
3//!
4//! Use this crate to wire the operational pieces around `runledger-core`
5//! handlers and `runledger-postgres` storage:
6//! - [`Supervisor`] starts and joins the worker, scheduler, and reaper loops
7//!   for a typical worker process
8//! - [`catalog::JobCatalog`] is the preferred startup API for handler
9//!   registration, definition sync, and catalog-validated enqueue helpers
10//! - [`registry::JobRegistry`] stores concrete handlers directly for advanced
11//!   setups that manage definitions separately
12//! - [`config::JobsConfig`] centralizes poll, lease, and concurrency settings
13//!
14//! A typical service builds a shared PostgreSQL pool, registers handlers in a
15//! [`catalog::JobCatalog`], syncs definitions during startup, and starts a
16//! [`Supervisor`] with [`SupervisorBuilder::with_catalog`]. Worker processes
17//! should call [`Supervisor::run_until_shutdown`] to observe task failures while
18//! still applying a bounded shutdown deadline. Use
19//! [`Supervisor::shutdown_with_timeout`] when shutdown is signaled externally, or
20//! [`Supervisor::shutdown`] when the caller already has an external shutdown
21//! budget or knows all loops will exit promptly.
22//!
23//! The lower-level [`worker::run_worker_loop`], [`scheduler::run_scheduler_loop`],
24//! and [`reaper::run_reaper_loop`] functions remain public for custom process
25//! orchestration, but [`Supervisor`] is the preferred runtime facade.
26//!
27//! # Copy-Paste Examples
28//!
29//! - [Run a worker binary](https://github.com/featherenvy/runledger/blob/master/runledger-runtime/examples/worker_binary.rs)
30//! - [Enqueue one job](https://github.com/featherenvy/runledger/blob/master/runledger-postgres/examples/enqueue_job.rs)
31//! - [Enqueue a workflow DAG](https://github.com/featherenvy/runledger/blob/master/runledger-postgres/examples/workflow_dag.rs)
32//! - [Use an external workflow gate](https://github.com/featherenvy/runledger/blob/master/runledger-postgres/examples/external_gate.rs)
33//! - [Create a scheduled job entrypoint](https://github.com/featherenvy/runledger/blob/master/runledger-postgres/examples/schedule_job.rs)
34//!
35//! # Prelude
36//!
37//! ```rust
38//! use runledger_runtime::prelude::*;
39//! ```
40//!
41//! The runtime prelude exports the worker-process facade and configuration
42//! types. Import `runledger_core::prelude::*` for handler contracts and
43//! `runledger_postgres::prelude::*` for persistence APIs.
44//!
45//! # Run A Worker Process
46//!
47//! ```rust,no_run
48//! # async fn demo(
49//! #     pool: runledger_postgres::DbPool,
50//! # ) -> std::result::Result<(), Box<dyn std::error::Error>> {
51//! use std::time::Duration;
52//!
53//! use runledger_core::prelude::*;
54//! use runledger_runtime::prelude::*;
55//!
56//! struct MyHandler;
57//! # #[async_trait::async_trait]
58//! # impl JobHandler for MyHandler {
59//! #     fn job_type(&self) -> JobType<'static> { JobType::new("jobs.example") }
60//! #     async fn execute(
61//! #         &self,
62//! #         _context: JobContext,
63//! #         _payload: serde_json::Value,
64//! #     ) -> std::result::Result<JobCompletion, JobFailure> { Ok(JobCompletion::success()) }
65//! # }
66//!
67//! let catalog = JobCatalog::new().job("jobs.example", MyHandler);
68//! catalog.sync_definitions(&pool).await?;
69//! let supervisor = Supervisor::builder(&pool, JobsConfig::from_env())?
70//!     .with_catalog(&catalog)
71//!     .build()?;
72//!
73//! supervisor
74//!     .run_until_shutdown(std::future::pending::<()>(), Duration::from_secs(30))
75//!     .await?;
76//! # Ok(())
77//! # }
78//! ```
79//!
80//! Use [`Supervisor::run_until_shutdown`] for ordinary worker binaries so the
81//! process observes internal runtime task failures while still applying a
82//! bounded shutdown deadline. Use the lower-level loop functions only for custom
83//! process orchestration.
84
85pub mod catalog;
86pub mod config;
87pub mod error;
88pub mod observer;
89pub mod reaper;
90pub mod registry;
91pub mod scheduler;
92mod shutdown;
93pub mod supervisor;
94mod task_group;
95pub mod worker;
96
97pub use error::{Error, ReaperError, Result, RuntimeError, SchedulerError, WorkerError};
98pub use observer::{
99    JobCompletionPersistFailedEvent, JobCompletionPersistenceOperation, JobContinuedEvent,
100    JobFailedEvent, JobFailureDisposition, JobLeaseLostEvent, JobLeaseReapedDisposition,
101    JobLeaseReapedEvent, JobLifecycleObserver, JobLifecycleObservers, JobRunningEvent,
102    JobSucceededEvent, ObservedJob,
103};
104pub use supervisor::{Supervisor, SupervisorBuilder, SupervisorShutdown};
105
106/// Common `runledger-runtime` imports for worker-process integration.
107///
108/// This prelude avoids generic `Result` or `Error` aliases so it can be
109/// glob-imported alongside the core and PostgreSQL preludes.
110pub mod prelude {
111    pub use crate::catalog::{
112        CatalogError, CatalogJobEnqueueInput, CatalogJobScheduleInput, CatalogJobScheduleSpec,
113        CatalogWorkflowDagBuilder, JobCatalog, JobCatalogDefaults, JobCatalogDefinitionOverrides,
114        JobCatalogExactSyncReport, JobCatalogScheduleSyncReport, JobCatalogScheduleSyncScope,
115        JobCatalogSyncReport, JobCatalogSyncScope,
116    };
117    pub use crate::config::JobsConfig;
118    pub use crate::error::{ReaperError, RuntimeError, SchedulerError, WorkerError};
119    pub use crate::observer::{
120        JobCompletionPersistFailedEvent, JobCompletionPersistenceOperation, JobContinuedEvent,
121        JobFailedEvent, JobFailureDisposition, JobLeaseLostEvent, JobLeaseReapedDisposition,
122        JobLeaseReapedEvent, JobLifecycleObserver, JobLifecycleObservers, JobRunningEvent,
123        JobSucceededEvent, ObservedJob,
124    };
125    pub use crate::registry::JobRegistry;
126    pub use crate::{RuntimeLoopExit, Supervisor, SupervisorBuilder, SupervisorShutdown};
127}
128
129/// Reason a low-level runtime loop exited.
130#[derive(Clone, Copy, Debug, Eq, PartialEq)]
131#[non_exhaustive]
132pub enum RuntimeLoopExit {
133    /// The loop observed a shutdown request or a closed shutdown channel.
134    Shutdown,
135    /// The loop rejected an invalid [`config::JobsConfig`] before polling.
136    InvalidConfig(config::JobsConfigValidationError),
137    /// The loop completed without observing shutdown. Supervisors treat this as
138    /// an unexpected task exit.
139    Completed,
140}
141
142#[cfg(test)]
143#[path = "../test_support.rs"]
144pub(crate) mod test_support;