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