Skip to main content

chronon_runtime/
runtime.rs

1//! Assembled [`Chronon`] runtime and background loop dispatch.
2
3use std::sync::Arc;
4
5use chronon_core::store::SchedulerStore;
6use chronon_executor::Executor;
7use chronon_scheduler::Scheduler;
8use tokio::sync::Notify;
9
10use crate::builder::DeploymentShape;
11use crate::coordinator_service::CoordinatorService;
12use crate::coordinator::run_coordinator_loops;
13use crate::embedded::run_embedded_loops;
14use crate::events::spawn_event_handler;
15use crate::worker::run_worker_loop;
16
17/// Assembled Chronon runtime: store, scheduler, executor, and deployment loops.
18///
19/// Built via [`ChrononBuilder`](crate::ChrononBuilder). Hosts typically:
20///
21/// 1. Upsert jobs through [`Self::coordinator_service`] (or HTTP / remote client).
22/// 2. Call [`Self::run`] to start shape-specific loops (or [`Self::tick_once`] in tests).
23/// 3. Call [`Self::shutdown`] on exit.
24///
25/// | [`DeploymentShape`](crate::DeploymentShape) | [`Self::run`] behavior |
26/// |---------------------------------------------|------------------------|
27/// | `Embedded` | Tick + worker in this process |
28/// | `CoordinatorOnly` | Tick + partitions only |
29/// | `Worker(pool)` | Claim + execute for `pool` |
30/// | `RemoteClient(_)` | **Error** — use [`crate::RemoteCoordinatorClient`] |
31///
32/// # Examples
33///
34/// ```
35/// use std::sync::Arc;
36/// use chronon_backend_mem::InMemorySchedulerStore;
37/// use chronon_runtime::{ChrononBuilder, DeploymentShape};
38///
39/// let chronon = ChrononBuilder::new()
40///     .scheduler_store(Arc::new(InMemorySchedulerStore::new()))
41///     .embedded()
42///     .build()
43///     .unwrap();
44/// assert_eq!(chronon.deployment, DeploymentShape::Embedded);
45/// let _ = chronon.coordinator_service();
46/// ```
47pub struct Chronon {
48    /// Shared persistence for jobs, runs, partitions, and workers.
49    pub store: Arc<dyn SchedulerStore>,
50    /// Tick engine and partition assigner handle.
51    pub scheduler: Arc<Scheduler>,
52    /// Script registry and async dispatch.
53    pub executor: Arc<Executor>,
54    /// Job/run CRUD facade over [`Self::store`].
55    pub coordinator: CoordinatorService,
56    /// Deployment shape selected at build time.
57    pub deployment: DeploymentShape,
58    shutdown: Arc<Notify>,
59    event_rx: Option<tokio::sync::mpsc::UnboundedReceiver<chronon_executor::ExecutorEvent>>,
60}
61
62impl Chronon {
63    pub(crate) fn new(
64        store: Arc<dyn SchedulerStore>,
65        scheduler: Arc<Scheduler>,
66        executor: Arc<Executor>,
67        deployment: DeploymentShape,
68        shutdown: Arc<Notify>,
69        event_rx: tokio::sync::mpsc::UnboundedReceiver<chronon_executor::ExecutorEvent>,
70    ) -> Self {
71        let coordinator = CoordinatorService::new(store.clone());
72        Self {
73            store,
74            scheduler,
75            executor,
76            coordinator,
77            deployment,
78            shutdown,
79            event_rx: Some(event_rx),
80        }
81    }
82
83    /// Signal all runtime loops to stop.
84    pub fn shutdown(&self) {
85        self.shutdown.notify_waiters();
86    }
87
88    /// Shared shutdown signal for background [`Self::run`] tasks (testkit / host wiring).
89    pub fn shutdown_handle(&self) -> Arc<Notify> {
90        Arc::clone(&self.shutdown)
91    }
92
93    /// Run deployment loops until [`Self::shutdown`] is called.
94    ///
95    /// Dispatches on [`Self::deployment`]:
96    ///
97    /// - [`DeploymentShape::Embedded`](crate::DeploymentShape::Embedded) — tick + worker
98    /// - [`DeploymentShape::CoordinatorOnly`](crate::DeploymentShape::CoordinatorOnly) — tick only
99    /// - [`DeploymentShape::Worker`](crate::DeploymentShape::Worker) — claim + execute
100    /// - [`DeploymentShape::RemoteClient`](crate::DeploymentShape::RemoteClient) — returns
101    ///   [`ChrononError::Internal`](chronon_core::ChrononError::Internal); use
102    ///   [`crate::RemoteCoordinatorClient`] instead
103    ///
104    /// Call `scheduler.init_partitions().await` before [`Self::run`] on coordinator /
105    /// embedded shapes so partition ownership is ready.
106    ///
107    /// # Examples
108    ///
109    /// Embedded: init partitions, then run until shutdown:
110    ///
111    /// ```no_run
112    /// use std::sync::Arc;
113    /// use chronon_backend_mem::InMemorySchedulerStore;
114    /// use chronon_runtime::ChrononBuilder;
115    ///
116    /// # async fn demo() -> chronon_core::Result<()> {
117    /// let mut chronon = ChrononBuilder::new()
118    ///     .scheduler_store(Arc::new(InMemorySchedulerStore::new()))
119    ///     .embedded()
120    ///     .build()?;
121    /// chronon.scheduler.init_partitions().await;
122    /// // chronon.run().await?;  // blocks until chronon.shutdown()
123    /// # let _ = chronon;
124    /// # Ok(())
125    /// # }
126    /// ```
127    ///
128    /// Runnable daemons: `cargo run -p uf-chronon --example coordinator_daemon --features postgres,redis`
129    /// and `worker_daemon`.
130    pub async fn run(&mut self) -> chronon_core::Result<()> {
131        if let Some(rx) = self.event_rx.take() {
132            spawn_event_handler(Arc::clone(&self.store), rx);
133        }
134
135        let shutdown = Arc::clone(&self.shutdown);
136        let scheduler = Arc::clone(&self.scheduler);
137        let executor = Arc::clone(&self.executor);
138        let telemetry = scheduler.telemetry();
139
140        match &self.deployment {
141            DeploymentShape::RemoteClient(_) => {
142                return Err(chronon_core::ChrononError::Internal(
143                    "remote client does not run local scheduler loop".into(),
144                ));
145            }
146            DeploymentShape::Embedded => {
147                run_embedded_loops(scheduler, executor, telemetry, shutdown).await;
148            }
149            DeploymentShape::CoordinatorOnly => {
150                run_coordinator_loops(scheduler, telemetry, shutdown).await;
151            }
152            DeploymentShape::Worker(pool) => {
153                run_worker_loop(
154                    self.store.clone(),
155                    executor,
156                    telemetry,
157                    pool.clone(),
158                    scheduler.instance_id().to_string(),
159                    shutdown,
160                )
161                .await;
162            }
163        }
164        Ok(())
165    }
166
167    /// Advance the scheduler by one tick (tests and manual stepping).
168    pub async fn tick_once(&self) -> chronon_core::Result<chronon_scheduler::TickResult> {
169        self.scheduler.tick_once().await
170    }
171
172    /// Job and run CRUD for HTTP handlers and host integration.
173    pub fn coordinator_service(&self) -> &CoordinatorService {
174        &self.coordinator
175    }
176
177    /// Script executor (registry + dispatch).
178    pub fn executor(&self) -> &Executor {
179        &self.executor
180    }
181}