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