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