1#![warn(unsafe_op_in_unsafe_fn)]
2
3use dora_core::{
4 config::{DataId, OperatorId},
5 descriptor::OperatorConfig,
6};
7use dora_message::daemon_to_node::{NodeConfig, RuntimeConfig};
8use dora_node_api::{DoraNode, Event};
9use dora_tracing::TracingBuilder;
10use eyre::{Context, Result, bail};
11use futures::{Stream, StreamExt};
12use futures_concurrency::stream::Merge;
13use operator::{OperatorEvent, StopReason, run_operator};
14
15use std::{
16 collections::{BTreeMap, BTreeSet, HashMap},
17 mem,
18};
19use tokio::{
20 runtime::Builder,
21 sync::{mpsc, oneshot},
22};
23use tokio_stream::wrappers::ReceiverStream;
24mod operator;
25
26pub fn main() -> eyre::Result<()> {
27 let config: RuntimeConfig = {
28 let raw = std::env::var("DORA_RUNTIME_CONFIG")
29 .wrap_err("env variable DORA_RUNTIME_CONFIG must be set")?;
30 serde_yaml::from_str(&raw).context("failed to deserialize runtime config")?
31 };
32 let RuntimeConfig {
33 node: config,
34 operators,
35 } = config;
36 let node_id = config.node_id.clone();
37 #[cfg(feature = "tracing")]
38 {
39 TracingBuilder::new(node_id.as_ref())
40 .with_stdout("warn", false)
41 .build()
42 .wrap_err("failed to set up tracing subscriber")?;
43 }
44
45 let dataflow_descriptor = serde_yaml::from_value(config.dataflow_descriptor.clone())
46 .context("failed to parse dataflow descriptor")?;
47
48 let operator_definition = if operators.is_empty() {
49 bail!("no operators");
50 } else if operators.len() > 1 {
51 bail!("multiple operators are not supported");
52 } else {
53 let mut ops = operators;
54 ops.remove(0)
55 };
56
57 let (operator_events_tx, events) = mpsc::channel(1);
58 let operator_id = operator_definition.id.clone();
59 let operator_events = ReceiverStream::new(events).map(move |event| RuntimeEvent::Operator {
60 id: operator_id.clone(),
61 event,
62 });
63
64 let tokio_runtime = Builder::new_multi_thread()
68 .worker_threads(1)
69 .enable_all()
70 .build()
71 .wrap_err("Could not build a tokio runtime.")?;
72
73 let mut operator_channels = HashMap::new();
74 let queue_sizes = queue_sizes(&operator_definition.config);
75 let (operator_channel, incoming_events) =
76 operator::channel::channel(tokio_runtime.handle(), queue_sizes);
77 operator_channels.insert(operator_definition.id.clone(), operator_channel);
78
79 tracing::info!("spawning main task");
80 let operator_config = [(
81 operator_definition.id.clone(),
82 operator_definition.config.clone(),
83 )]
84 .into_iter()
85 .collect();
86 let (init_done_tx, init_done) = oneshot::channel();
87 let main_task = std::thread::spawn(move || -> Result<()> {
88 tokio_runtime.block_on(run(
89 operator_config,
90 config,
91 operator_events,
92 operator_channels,
93 init_done,
94 ))
95 });
96
97 let operator_id = operator_definition.id.clone();
98 run_operator(
99 &node_id,
100 operator_definition,
101 incoming_events,
102 operator_events_tx,
103 init_done_tx,
104 &dataflow_descriptor,
105 )
106 .wrap_err_with(|| format!("failed to run operator {operator_id}"))?;
107
108 match main_task.join() {
109 Ok(result) => result.wrap_err("main task failed")?,
110 Err(panic) => std::panic::resume_unwind(panic),
111 }
112
113 Ok(())
114}
115
116fn queue_sizes(
117 config: &OperatorConfig,
118) -> std::collections::BTreeMap<DataId, (usize, dora_message::config::QueuePolicy)> {
119 let mut sizes = BTreeMap::new();
120 for (input_id, input) in &config.inputs {
121 let queue_size = input
122 .queue_size
123 .unwrap_or(dora_message::config::DEFAULT_QUEUE_SIZE);
124 let policy = input.queue_policy.unwrap_or_default();
125 sizes.insert(input_id.clone(), (queue_size, policy));
126 }
127 sizes
128}
129
130#[tracing::instrument(skip(operator_events, operator_channels), level = "trace")]
131async fn run(
132 operators: HashMap<OperatorId, OperatorConfig>,
133 config: NodeConfig,
134 operator_events: impl Stream<Item = RuntimeEvent> + Unpin,
135 mut operator_channels: HashMap<OperatorId, flume::Sender<Event>>,
136 init_done: oneshot::Receiver<Result<()>>,
137) -> eyre::Result<()> {
138 #[cfg(feature = "metrics")]
147 if std::env::var("DORA_OTLP_ENDPOINT").is_ok() {
148 use dora_metrics::run_metrics_monitor;
149
150 let meter_id = config.node_id.to_string();
151 tokio::spawn(async move {
152 if let Err(e) = run_metrics_monitor(meter_id)
153 .await
154 .wrap_err("metrics monitor exited unexpectedly")
155 {
156 tracing::warn!("metrics monitor failed: {e:#}");
157 }
158 });
159 }
160 init_done
161 .await
162 .wrap_err("the `init_done` channel was closed unexpectedly")?
163 .wrap_err("failed to init an operator")?;
164 tracing::info!("All operators are ready, starting runtime");
165
166 let (mut node, mut daemon_events) = DoraNode::init(config)?;
167 let (daemon_events_tx, daemon_event_stream) = flume::bounded(1);
168 tokio::task::spawn_blocking(move || {
169 while let Some(event) = daemon_events.recv() {
170 if daemon_events_tx.send(RuntimeEvent::Event(event)).is_err() {
171 break;
172 }
173 }
174 });
175 let mut events = (operator_events, daemon_event_stream.into_stream()).merge();
176
177 let mut open_operator_inputs: HashMap<_, BTreeSet<_>> = operators
178 .iter()
179 .map(|(id, config)| (id, config.inputs.keys().collect()))
180 .collect();
181
182 while let Some(event) = events.next().await {
183 match event {
184 RuntimeEvent::Operator {
185 id: operator_id,
186 event,
187 } => match event {
188 OperatorEvent::Error(err) => {
189 bail!(err.wrap_err(format!(
190 "operator {}/{operator_id} raised an error",
191 node.id()
192 )))
193 }
194 OperatorEvent::Panic(payload) => {
195 let message = payload
196 .downcast_ref::<&str>()
197 .map(|s| s.to_string())
198 .or_else(|| payload.downcast_ref::<String>().cloned())
199 .unwrap_or_else(|| format!("{payload:?}"));
200 bail!("operator {operator_id} panicked: {message}");
201 }
202 OperatorEvent::Finished { reason } => {
203 if let StopReason::ExplicitStopAll = reason {
204 bail!(
205 "operator {operator_id} requested StopAll, which is not yet implemented"
206 );
207 }
208
209 let Some(config) = operators.get(&operator_id) else {
210 tracing::warn!(
211 "received Finished event for unknown operator `{operator_id}`"
212 );
213 continue;
214 };
215 let outputs = config
216 .outputs
217 .iter()
218 .map(|output_id| operator_output_id(&operator_id, output_id))
219 .collect();
220 let result;
221 (node, result) = tokio::task::spawn_blocking(move || {
222 let result = node.close_outputs(outputs);
223 (node, result)
224 })
225 .await
226 .wrap_err("failed to wait for close_outputs task")?;
227 result.wrap_err("failed to close outputs of finished operator")?;
228
229 operator_channels.remove(&operator_id);
230
231 if operator_channels.is_empty() {
232 break;
233 }
234 }
235 OperatorEvent::Output {
236 output_id,
237 parameters,
238 arrow_array,
239 } => {
240 let output_id = operator_output_id(&operator_id, &output_id);
241 let result;
242 (node, result) = tokio::task::spawn_blocking(move || {
243 let array = dora_node_api::arrow::array::make_array(arrow_array);
244 let result = node.send_output(output_id, parameters, array);
245 (node, result)
246 })
247 .await
248 .wrap_err("failed to wait for send_output task")?;
249 result.wrap_err("failed to send node output")?;
250 }
251 },
252 RuntimeEvent::Event(Event::Stop(cause)) => {
253 for (_, channel) in operator_channels.drain() {
255 let _ = channel.send_async(Event::Stop(cause.clone())).await;
256 }
257 }
258 RuntimeEvent::Event(Event::Reload {
259 operator_id: Some(operator_id),
260 }) => {
261 let Some(operator_channel) = operator_channels.get(&operator_id) else {
262 tracing::warn!("received Reload event for unknown operator `{operator_id}`");
263 continue;
264 };
265 let _ = operator_channel
266 .send_async(Event::Reload {
267 operator_id: Some(operator_id),
268 })
269 .await;
270 }
271 RuntimeEvent::Event(Event::Reload { operator_id: None }) => {
272 tracing::warn!("Reloading runtime nodes is not supported");
273 }
274 RuntimeEvent::Event(Event::Input { id, metadata, data }) => {
275 let Some((operator_id, input_id)) = id.as_str().split_once('/') else {
276 tracing::warn!("received non-operator input {id}");
277 continue;
278 };
279 let operator_id = OperatorId::from(operator_id.to_owned());
280 let input_id = DataId::from(input_id.to_owned());
281 let Some(operator_channel) = operator_channels.get(&operator_id) else {
282 tracing::warn!("received input {id} for unknown operator");
283 continue;
284 };
285
286 if let Err(err) = operator_channel
287 .send_async(Event::Input {
288 id: input_id.clone(),
289 metadata,
290 data,
291 })
292 .await
293 .wrap_err_with(|| {
294 format!("failed to send input `{input_id}` to operator `{operator_id}`")
295 })
296 {
297 tracing::warn!("{err}");
298 }
299 }
300 RuntimeEvent::Event(Event::InputClosed { id }) => {
301 let Some((operator_id, input_id)) = id.as_str().split_once('/') else {
302 tracing::warn!("received InputClosed event for non-operator input {id}");
303 continue;
304 };
305 let operator_id = OperatorId::from(operator_id.to_owned());
306 let input_id = DataId::from(input_id.to_owned());
307
308 let Some(operator_channel) = operator_channels.get(&operator_id) else {
309 tracing::warn!("received input {id} for unknown operator");
310 continue;
311 };
312 if let Err(err) = operator_channel
313 .send_async(Event::InputClosed {
314 id: input_id.clone(),
315 })
316 .await
317 .wrap_err_with(|| {
318 format!(
319 "failed to send InputClosed({input_id}) to operator `{operator_id}`"
320 )
321 })
322 {
323 tracing::warn!("{err}");
324 }
325
326 if let Some(open_inputs) = open_operator_inputs.get_mut(&operator_id) {
327 open_inputs.remove(&input_id);
328 if open_inputs.is_empty() {
329 tracing::trace!(
331 "all inputs of operator {}/{operator_id} were closed -> closing event channel",
332 node.id()
333 );
334 open_operator_inputs.remove(&operator_id);
335 operator_channels.remove(&operator_id);
336 }
337 }
338 }
339 RuntimeEvent::Event(Event::Error(err)) => eyre::bail!("received error event: {err}"),
340 RuntimeEvent::Event(other) => {
341 tracing::warn!("received unknown event `{other:?}`");
342 }
343 }
344 }
345
346 mem::drop(events);
347
348 Ok(())
349}
350
351fn operator_output_id(operator_id: &OperatorId, output_id: &DataId) -> DataId {
352 DataId::from(format!("{operator_id}/{output_id}"))
353}
354
355#[derive(Debug)]
356enum RuntimeEvent {
357 Operator {
358 id: OperatorId,
359 event: OperatorEvent,
360 },
361 Event(Event),
362}