1#[cfg(all(reifydb_target = "host", not(reifydb_dst)))]
5pub mod extern_c;
6#[cfg(all(reifydb_target = "host", not(reifydb_dst)))]
7pub mod extern_rust;
8pub mod factory;
9pub mod shutdown;
10
11use std::{
12 any::Any,
13 path::PathBuf,
14 sync::{
15 Arc,
16 atomic::{AtomicBool, Ordering},
17 },
18};
19
20#[cfg(all(reifydb_target = "host", not(reifydb_dst)))]
21use extern_c::load_extern_c_operators;
22#[cfg(all(reifydb_target = "host", not(reifydb_dst)))]
23use extern_rust::load_extern_rust_operators;
24use reifydb_cdc::{
25 consume::{
26 backlog::FlowBacklog,
27 watermark::{CdcConsumerWatermark, FlowCaughtUpWatermark},
28 },
29 storage::CdcStore,
30};
31use reifydb_core::{
32 actors::flow::{FlowSupervisorHandle, FlowSupervisorMessage},
33 event::operator::OperatorLoadedEvent,
34 interface::{
35 WithEventBus,
36 catalog::{
37 config::{ConfigKey, GetConfig},
38 flow::FlowId,
39 },
40 cdc::CdcConsumerId,
41 flow::FlowWatermarkSampler,
42 version::{ComponentType, HasVersion, SystemVersion},
43 },
44 lifecycle::watermark::ConsumerPositions,
45 metrics::registry::MetricsRegistry,
46 util::ioc::IocContainer,
47};
48use reifydb_engine::{engine::StandardEngine, vm::flow_lineage::ViewLineage};
49use reifydb_flow::{
50 operator::metrics::{OperatorSampleCollector, OperatorSampleRegistry},
51 transaction::substrate::FlowSubstrate,
52};
53use reifydb_runtime::{actor::system::ActorSpawner, context::clock::Clock, shutdown::Shutdown, sync::mutex::Mutex};
54use reifydb_sub_api::subsystem::{HealthStatus, Subsystem};
55use reifydb_transaction::{
56 group::{GroupCommitBegin, GroupCommitHandle},
57 transaction::Transaction,
58};
59use reifydb_value::{
60 Result,
61 byte_size::ByteSize,
62 value::{duration::Duration, identity::IdentityId},
63};
64use tracing::warn;
65
66use crate::{
67 builder::{CustomOperators, FlowConfig},
68 catalog::FlowCatalog,
69 commit::{
70 committer::{Committer, CommitterActor, CommitterHandle},
71 quiescence::FlowMaterialization,
72 },
73 control::{
74 health::FlowHealthRegistry,
75 supervisor::{FlowSupervisor, FlowSupervisorParams},
76 },
77 discovery::loader::{LoaderActor, LoaderHandle, LoaderMetrics},
78 progress::{
79 frontier::ControlFrontier,
80 tracker::{FlowPositionTracker, ObjectVersionTracker},
81 watermark::compute_flow_watermarks,
82 },
83 subsystem::shutdown::FlowShutdownState,
84};
85
86const FLOW_CHECKPOINT_LAG: u64 = 10_000;
88const FLOW_CHECKPOINT_MAX_AGE_MS: i64 = 5_000;
89const FLOW_FRONTIER_PERSIST_MS: i64 = 5_000;
90
91pub struct FlowSubsystem {
92 flow_scope: ActorSpawner,
93 loader_handle: Mutex<Option<LoaderHandle>>,
94 committer_handle: Mutex<Option<CommitterHandle>>,
95 supervisor_handle: Mutex<Option<FlowSupervisorHandle>>,
96 view_lineage: ViewLineage,
97 health: FlowHealthRegistry,
98 shutdown_state: FlowShutdownState,
99 running: AtomicBool,
100}
101
102impl FlowSubsystem {
103 pub fn publish_operator_catalog(config: &FlowConfig, engine: &StandardEngine) {
104 Self::publish_custom_operators(&config.custom_operators, engine);
105 }
106
107 pub fn new(config: FlowConfig, engine: StandardEngine, ioc: &IocContainer) -> Result<Self> {
108 Self::maybe_load_extern_operators(&config, &engine);
109
110 let clock = ioc.resolve::<Clock>().expect("Clock must be registered");
111 let spawner = ioc.resolve::<ActorSpawner>().expect("ActorSpawner must be registered");
112 let custom_operators = config.custom_operators;
113 let substrate = FlowSubstrate::with_dictionary(engine.dictionary_allocators(), engine.operator_state());
114 let object_tracker = ObjectVersionTracker::new();
115 let flow_tracker = FlowPositionTracker::new();
116 let cdc_store = ioc.resolve::<CdcStore>().expect("CdcStore must be registered");
117
118 let flow_scope = spawner.scope();
119 let flow_catalog = FlowCatalog::new(engine.catalog());
120
121 let group_commit = ioc.try_resolve::<GroupCommitHandle>().unwrap_or_else(|| {
122 let begin_engine = engine.clone();
123 let begin: GroupCommitBegin =
124 Arc::new(move || begin_engine.begin_command(IdentityId::system()));
125 GroupCommitHandle::inline(begin)
126 });
127 let poll_frontier = CdcConsumerWatermark::default();
128 let materialization = FlowMaterialization::new(poll_frontier.clone(), flow_tracker.clone());
129 let committer = Committer::new(
130 flow_tracker.clone(),
131 materialization.clone(),
132 substrate.operators.clone().expect("the flow substrate is built with an operator store"),
133 );
134 let committer_handle =
135 flow_scope.spawn_flow("flow-committer", CommitterActor::new(committer, group_commit));
136 let committer_ref = committer_handle.actor_ref().clone();
137
138 let health = FlowHealthRegistry::new();
139 let operator_samples = OperatorSampleRegistry::new();
140 let metrics_registry = ioc.resolve::<MetricsRegistry>().expect("MetricsRegistry must be registered");
141 metrics_registry
142 .register_operator_collector(Arc::new(OperatorSampleCollector::new(operator_samples.clone())));
143 let view_lineage = engine.view_lineage();
144
145 let backlog = ioc.resolve::<FlowBacklog>().expect("FlowBacklog must be registered");
146 metrics_registry.register_collector(Arc::new(backlog.clone()));
147 let loader_metrics = LoaderMetrics::default();
148 metrics_registry.register_collector(Arc::new(loader_metrics.clone()));
149 let control = ControlFrontier::new();
150 let loader_handle =
151 flow_scope.spawn_flow("flow-loader", LoaderActor::new(cdc_store.hot_reader(), loader_metrics));
152 let pull_batch_bytes =
153 ByteSize::from_bytes(engine.catalog().get_config_uint8(ConfigKey::FlowPullBatchBytes));
154 let load_batch_bytes =
155 ByteSize::from_bytes(engine.catalog().get_config_uint8(ConfigKey::FlowLoadBatchBytes));
156
157 let flow_consumer_id = CdcConsumerId::flow_consumer();
158 let supervisor_handle = flow_scope.spawn_flow(
159 "flow-supervisor",
160 FlowSupervisor::new(FlowSupervisorParams {
161 engine: engine.clone(),
162 flow_catalog: flow_catalog.clone(),
163 committer: committer_ref,
164 backlog: backlog.clone(),
165 loader: loader_handle.actor_ref().clone(),
166 control,
167 poll_frontier: poll_frontier.clone(),
168 view_lineage: view_lineage.clone(),
169 tracker: object_tracker.clone(),
170 flow_tracker: flow_tracker.clone(),
171 health: health.clone(),
172 custom_operators: custom_operators.clone(),
173 substrate: substrate.clone(),
174 operator_samples: operator_samples.clone(),
175 clock: clock.clone(),
176 spawner: flow_scope.clone(),
177 consumer_id: flow_consumer_id,
178 pull_batch_bytes,
179 load_batch_bytes,
180 checkpoint_lag: FLOW_CHECKPOINT_LAG,
181 checkpoint_max_age: Duration::from_milliseconds(FLOW_CHECKPOINT_MAX_AGE_MS).unwrap(),
182 frontier_persist: Duration::from_milliseconds(FLOW_FRONTIER_PERSIST_MS).unwrap(),
183 }),
184 );
185
186 Self::register_watermark_sampler(
187 ioc,
188 &engine,
189 &object_tracker,
190 &flow_tracker,
191 &flow_catalog,
192 &materialization,
193 );
194
195 ioc.register_service::<FlowCaughtUpWatermark>(FlowCaughtUpWatermark::new(move || {
196 materialization.caught_up()
197 }));
198
199 ioc.register_service::<Arc<dyn ConsumerPositions>>(Arc::new(flow_tracker.clone()));
200
201 let scan_from = engine.current_version().ok();
202 let bootstrap_flows = Self::bootstrap_flows(&engine);
203 let _ = supervisor_handle.actor_ref().send(FlowSupervisorMessage::Bootstrap {
204 flows: bootstrap_flows,
205 scan_from,
206 });
207
208 let supervisor_ref = supervisor_handle.actor_ref().clone();
209 backlog.set_waker(move || {
210 let _ = supervisor_ref.send(FlowSupervisorMessage::Wake);
211 });
212 let _ = supervisor_handle.actor_ref().send(FlowSupervisorMessage::Wake);
213
214 Ok(Self {
215 flow_scope,
216 loader_handle: Mutex::new(Some(loader_handle)),
217 committer_handle: Mutex::new(Some(committer_handle)),
218 supervisor_handle: Mutex::new(Some(supervisor_handle)),
219 view_lineage,
220 health,
221 shutdown_state: FlowShutdownState::new(engine, substrate),
222 running: AtomicBool::new(true),
223 })
224 }
225
226 pub fn persist_frontiers(&self) {
227 if self.is_running() {
228 self.shutdown_state.persist_frontiers();
229 }
230 }
231
232 #[inline]
233 fn publish_custom_operators(custom_operators: &CustomOperators, engine: &StandardEngine) {
234 let event_bus = engine.event_bus();
235 for (name, entry) in custom_operators.iter() {
236 event_bus.emit(OperatorLoadedEvent::new(
237 name.clone(),
238 PathBuf::new(),
239 entry.abi,
240 entry.version.clone(),
241 entry.description.clone(),
242 entry.input.clone(),
243 entry.output.clone(),
244 entry.capabilities,
245 ));
246 }
247 event_bus.wait_for_completion();
248 }
249
250 #[inline]
251 fn maybe_load_extern_operators(config: &FlowConfig, engine: &StandardEngine) {
252 #[cfg(all(reifydb_target = "host", not(reifydb_dst)))]
253 if let Some(ref operators_dir) = config.operators_dir {
254 let event_bus = engine.event_bus();
255 if let Err(e) = load_extern_rust_operators(operators_dir, event_bus) {
256 panic!("Failed to load extern-Rust operators from {:?}: {}", operators_dir, e);
257 }
258 if let Err(e) = load_extern_c_operators(operators_dir, event_bus) {
259 panic!("Failed to load extern-C operators from {:?}: {}", operators_dir, e);
260 }
261 event_bus.wait_for_completion();
262 }
263 #[cfg(not(all(reifydb_target = "host", not(reifydb_dst))))]
264 {
265 let _ = (config, engine);
266 }
267 }
268
269 #[inline]
270 fn register_watermark_sampler(
271 ioc: &IocContainer,
272 engine: &StandardEngine,
273 object_tracker: &ObjectVersionTracker,
274 flow_tracker: &FlowPositionTracker,
275 flow_catalog: &FlowCatalog,
276 materialization: &FlowMaterialization,
277 ) {
278 ioc.register_service::<FlowWatermarkSampler>(FlowWatermarkSampler::new({
279 let engine = engine.clone();
280 let tracker = object_tracker.clone();
281 let flow_tracker = flow_tracker.clone();
282 let flow_catalog = flow_catalog.clone();
283 let materialization = materialization.clone();
284 move || {
285 compute_flow_watermarks(&tracker, &flow_tracker, &flow_catalog, || {
286 engine.done_until().max(materialization.output_frontier())
287 })
288 }
289 }));
290 }
291
292 #[inline]
293 fn bootstrap_flows(engine: &StandardEngine) -> Vec<FlowId> {
294 let mut bootstrap_flows = Vec::new();
295 if let Ok(mut query) = engine.begin_query(IdentityId::system()) {
296 match engine.catalog().list_flows_all(&mut Transaction::Query(&mut query)) {
297 Ok(existing_flows) => {
298 bootstrap_flows.extend(existing_flows.into_iter().map(|existing| existing.id));
299 }
300 Err(e) => warn!(error = %e, "failed to list flows during bootstrap"),
301 }
302 }
303 bootstrap_flows
304 }
305}
306
307impl Shutdown for FlowSubsystem {
308 fn shutdown(&self) {
309 if self.running.compare_exchange(true, false, Ordering::AcqRel, Ordering::Acquire).is_err() {
310 return;
311 }
312
313 self.flow_scope.shutdown();
314
315 if let Some(handle) = self.supervisor_handle.lock().take() {
316 let _ = handle.join();
317 }
318
319 if let Some(handle) = self.loader_handle.lock().take() {
320 let _ = handle.join();
321 }
322
323 if let Some(handle) = self.committer_handle.lock().take() {
324 let _ = handle.join();
325 }
326
327 self.view_lineage.publish(Default::default());
328 }
329}
330
331impl Subsystem for FlowSubsystem {
332 fn name(&self) -> &'static str {
333 "flow"
334 }
335
336 fn is_running(&self) -> bool {
337 self.running.load(Ordering::Acquire)
338 }
339
340 fn health_status(&self) -> HealthStatus {
341 if !self.is_running() {
342 return HealthStatus::Unknown;
343 }
344 let poisoned = self.health.poisoned();
345 if poisoned.is_empty() {
346 return HealthStatus::Healthy;
347 }
348 let flows: Vec<String> =
349 poisoned.iter().map(|(id, reason)| format!("flow {}: {}", id.0, reason)).collect();
350 HealthStatus::Degraded {
351 description: format!("{} deferred flow(s) poisoned: {}", poisoned.len(), flows.join("; ")),
352 }
353 }
354
355 fn as_any(&self) -> &dyn Any {
356 self
357 }
358}
359
360impl HasVersion for FlowSubsystem {
361 fn version(&self) -> SystemVersion {
362 SystemVersion {
363 name: env!("CARGO_PKG_NAME")
364 .strip_prefix("reifydb-")
365 .unwrap_or(env!("CARGO_PKG_NAME"))
366 .to_string(),
367 version: env!("CARGO_PKG_VERSION").to_string(),
368 description: "Data flow and stream processing subsystem".to_string(),
369 r#type: ComponentType::Subsystem,
370 }
371 }
372}
373
374impl Drop for FlowSubsystem {
375 fn drop(&mut self) {
376 self.shutdown();
377 }
378}