1#![warn(unsafe_op_in_unsafe_fn)]
12
13use dora_core::{
14 config::{DataId, OperatorId},
15 descriptor::OperatorConfig,
16};
17use dora_message::daemon_to_node::{NodeConfig, RuntimeConfig};
18use dora_node_api::{DoraNode, Event, StopCause};
19use dora_tracing::TracingBuilder;
20use eyre::{Context, Result, bail};
21use futures::{Stream, StreamExt};
22use futures_concurrency::stream::Merge;
23use std::{
24 collections::{BTreeMap, BTreeSet, HashMap, VecDeque},
25 mem,
26 sync::{Arc, Mutex, OnceLock},
27 time::{Duration, Instant},
28};
29use tokio::{
30 runtime::Builder,
31 sync::{mpsc, oneshot},
32};
33use tokio_stream::wrappers::ReceiverStream;
34
35mod channel;
36mod operator;
37
38pub use operator::{
39 OperatorEvent, OperatorRunner, RunnerGuard, RuntimeHandle, SharedAllocator, StopReason,
40};
41
42pub fn main(runner: impl OperatorRunner) -> eyre::Result<()> {
49 let config: RuntimeConfig = {
50 let raw = std::env::var("DORA_RUNTIME_CONFIG")
51 .wrap_err("env variable DORA_RUNTIME_CONFIG must be set")?;
52 serde_yaml::from_str(&raw).context("failed to deserialize runtime config")?
53 };
54 let RuntimeConfig {
55 node: config,
56 operators,
57 } = config;
58 let node_id = config.node_id.clone();
59 #[cfg(feature = "tracing")]
60 {
61 TracingBuilder::new(node_id.as_ref())
62 .with_stdout("warn", false)
63 .build()
64 .wrap_err("failed to set up tracing subscriber")?;
65 }
66
67 let dataflow_descriptor = serde_yaml::from_value(config.dataflow_descriptor.clone())
68 .context("failed to parse dataflow descriptor")?;
69
70 let operator_definition = if operators.is_empty() {
71 bail!("no operators");
72 } else if operators.len() > 1 {
73 bail!("multiple operators are not supported");
74 } else {
75 let mut ops = operators;
76 ops.remove(0)
77 };
78
79 let (operator_events_tx, events) = mpsc::channel(1);
80 let operator_id = operator_definition.id.clone();
81 let operator_events = ReceiverStream::new(events).map(move |event| RuntimeEvent::Operator {
82 id: operator_id.clone(),
83 event,
84 });
85
86 let tokio_runtime = Builder::new_multi_thread()
90 .worker_threads(1)
91 .enable_all()
92 .build()
93 .wrap_err("Could not build a tokio runtime.")?;
94
95 let mut operator_channels = HashMap::new();
96 let queue_sizes = queue_sizes(&operator_definition.config);
97 let (operator_channel, incoming_events) = channel::channel(tokio_runtime.handle(), queue_sizes);
98 operator_channels.insert(operator_definition.id.clone(), operator_channel);
99
100 tracing::info!("spawning main task");
101 let operator_config = [(
102 operator_definition.id.clone(),
103 operator_definition.config.clone(),
104 )]
105 .into_iter()
106 .collect();
107 let (init_done_tx, init_done) = oneshot::channel();
108 let allocator: SharedAllocator = Arc::new(OnceLock::new());
111 let run_allocator = allocator.clone();
112 let main_task = std::thread::spawn(move || -> Result<()> {
113 tokio_runtime.block_on(run(
114 operator_config,
115 config,
116 operator_events,
117 operator_channels,
118 init_done,
119 run_allocator,
120 ))
121 });
122
123 let operator_id = operator_definition.id.clone();
124 let _operator_guard = runner
131 .run_operator(
132 &node_id,
133 operator_definition,
134 incoming_events,
135 RuntimeHandle::new(operator_events_tx, allocator),
136 init_done_tx,
137 &dataflow_descriptor,
138 )
139 .wrap_err_with(|| format!("failed to run operator {operator_id}"))?;
140
141 match main_task.join() {
142 Ok(result) => result.wrap_err("main task failed")?,
143 Err(panic) => std::panic::resume_unwind(panic),
144 }
145
146 Ok(())
150}
151
152fn queue_sizes(
153 config: &OperatorConfig,
154) -> std::collections::BTreeMap<DataId, (usize, dora_message::config::QueuePolicy)> {
155 let mut sizes = BTreeMap::new();
156 for (input_id, input) in &config.inputs {
157 let queue_size = input
158 .queue_size
159 .unwrap_or(dora_message::config::DEFAULT_QUEUE_SIZE);
160 let policy = input.queue_policy.unwrap_or_default();
161 sizes.insert(input_id.clone(), (queue_size, policy));
162 }
163 sizes
164}
165
166#[tracing::instrument(skip(operator_events, operator_channels, allocator), level = "trace")]
167async fn run(
168 operators: HashMap<OperatorId, OperatorConfig>,
169 config: NodeConfig,
170 operator_events: impl Stream<Item = RuntimeEvent> + Unpin,
171 mut operator_channels: HashMap<OperatorId, flume::Sender<Event>>,
172 init_done: oneshot::Receiver<Result<()>>,
173 allocator: SharedAllocator,
174) -> eyre::Result<()> {
175 #[cfg(feature = "metrics")]
184 if let Ok(endpoint) = std::env::var("DORA_OTLP_ENDPOINT") {
185 use dora_metrics::run_metrics_monitor;
186
187 let meter_id = config.node_id.to_string();
188 tokio::spawn(async move {
189 if let Err(e) = run_metrics_monitor(meter_id, &endpoint)
190 .await
191 .wrap_err("metrics monitor exited unexpectedly")
192 {
193 tracing::warn!("metrics monitor failed: {e:#}");
194 }
195 });
196 }
197 init_done
198 .await
199 .wrap_err("the `init_done` channel was closed unexpectedly")?
200 .wrap_err("failed to init an operator")?;
201 tracing::info!("All operators are ready, starting runtime");
202
203 let (mut node, mut daemon_events) = DoraNode::init(config)?;
204 let _ = allocator.set(node.sample_allocator());
207 let (daemon_events_tx, daemon_event_stream) = flume::bounded(1);
208 tokio::task::spawn_blocking(move || {
209 while let Some(event) = daemon_events.recv() {
210 if daemon_events_tx.send(RuntimeEvent::Event(event)).is_err() {
211 break;
212 }
213 }
214 });
215 let mut events = (operator_events, daemon_event_stream.into_stream()).merge();
216
217 let mut open_operator_inputs: HashMap<_, BTreeSet<_>> = operators
218 .iter()
219 .map(|(id, config)| (id, config.inputs.keys().collect()))
220 .collect();
221
222 let activity = Arc::new(Mutex::new(LoopActivity::Idle));
228 {
229 let activity = activity.clone();
230 let node_id = node.id().to_string();
231 tokio::spawn(async move {
232 let mut ticker = tokio::time::interval(Duration::from_secs(2));
233 ticker.tick().await; loop {
235 ticker.tick().await;
236 let stalled = match &*lock(&activity) {
237 LoopActivity::Handling { since, what }
238 if since.elapsed() > Duration::from_secs(3) =>
239 {
240 Some((since.elapsed(), *what))
241 }
242 _ => None,
243 };
244 if let Some((elapsed, what)) = stalled {
245 tracing::warn!(
246 "runtime `{node_id}` main loop stalled for {:.0}s while handling {what} \
247 (dora-rs/dora#2742 diagnostic)",
248 elapsed.as_secs_f32()
249 );
250 }
251 }
252 });
253 }
254
255 let mut pending: VecDeque<RuntimeEvent> = VecDeque::new();
259
260 loop {
261 *lock(&activity) = LoopActivity::Idle;
262 let next = match pending.pop_front() {
263 buffered @ Some(_) => buffered,
264 None => events.next().await,
265 };
266 let Some(event) = next else { break };
267 *lock(&activity) = LoopActivity::Handling {
268 since: Instant::now(),
269 what: describe_runtime_event(&event),
270 };
271 match event {
272 RuntimeEvent::Operator {
273 id: operator_id,
274 event,
275 } => match event {
276 OperatorEvent::Error(err) => {
277 bail!(err.wrap_err(format!(
278 "operator {}/{operator_id} raised an error",
279 node.id()
280 )))
281 }
282 OperatorEvent::Panic(payload) => {
283 let message = payload
284 .downcast_ref::<&str>()
285 .map(|s| s.to_string())
286 .or_else(|| payload.downcast_ref::<String>().cloned())
287 .unwrap_or_else(|| format!("{payload:?}"));
288 bail!("operator {operator_id} panicked: {message}");
289 }
290 OperatorEvent::Finished { reason } => {
291 if let StopReason::ExplicitStopAll = reason {
292 bail!(
293 "operator {operator_id} requested StopAll, which is not yet implemented"
294 );
295 }
296
297 let Some(config) = operators.get(&operator_id) else {
298 tracing::warn!(
299 "received Finished event for unknown operator `{operator_id}`"
300 );
301 continue;
302 };
303 let outputs = config
304 .outputs
305 .iter()
306 .map(|output_id| operator_output_id(&operator_id, output_id))
307 .collect();
308 let result;
309 (node, result) = tokio::task::spawn_blocking(move || {
310 let result = node.close_outputs(outputs);
311 (node, result)
312 })
313 .await
314 .wrap_err("failed to wait for close_outputs task")?;
315 result.wrap_err("failed to close outputs of finished operator")?;
316
317 operator_channels.remove(&operator_id);
318
319 if operator_channels.is_empty() {
320 break;
321 }
322 }
323 OperatorEvent::Output {
324 output_id,
325 parameters,
326 encoded,
327 } => {
328 let output_id = operator_output_id(&operator_id, &output_id);
329 let mut send = tokio::task::spawn_blocking(move || {
330 let result = node.send_output_encoded(output_id, parameters, encoded);
331 (node, result)
332 });
333 let result;
334 (node, result) = await_send_watching_for_stop(
335 &mut send,
336 &mut events,
337 &mut pending,
338 &mut operator_channels,
339 )
340 .await
341 .wrap_err("failed to wait for send_output task")?;
342 result.wrap_err("failed to send node output")?;
343 }
344 },
345 RuntimeEvent::Event(Event::Stop(cause)) => {
346 forward_stop(&mut operator_channels, &cause).await;
347 }
348 RuntimeEvent::Event(Event::Reload {
349 operator_id: Some(operator_id),
350 }) => {
351 let Some(operator_channel) = operator_channels.get(&operator_id) else {
352 tracing::warn!("received Reload event for unknown operator `{operator_id}`");
353 continue;
354 };
355 let _ = operator_channel
356 .send_async(Event::Reload {
357 operator_id: Some(operator_id),
358 })
359 .await;
360 }
361 RuntimeEvent::Event(Event::Reload { operator_id: None }) => {
362 tracing::warn!("Reloading runtime nodes is not supported");
363 }
364 RuntimeEvent::Event(Event::Input { id, metadata, data }) => {
365 let Some((operator_id, input_id)) = id.as_str().split_once('/') else {
366 tracing::warn!("received non-operator input {id}");
367 continue;
368 };
369 let operator_id = OperatorId::from(operator_id.to_owned());
370 let input_id = DataId::from(input_id.to_owned());
371 let Some(operator_channel) = operator_channels.get(&operator_id) else {
372 tracing::warn!("received input {id} for unknown operator");
373 continue;
374 };
375
376 if let Err(err) = operator_channel
377 .send_async(Event::Input {
378 id: input_id,
379 metadata,
380 data,
381 })
382 .await
383 .wrap_err_with(|| {
384 format!("failed to send input `{id}` to operator `{operator_id}`")
388 })
389 {
390 tracing::warn!("{err}");
391 }
392 }
393 RuntimeEvent::Event(Event::InputClosed { id }) => {
394 let Some((operator_id, input_id)) = id.as_str().split_once('/') else {
395 tracing::warn!("received InputClosed event for non-operator input {id}");
396 continue;
397 };
398 let operator_id = OperatorId::from(operator_id.to_owned());
399 let input_id = DataId::from(input_id.to_owned());
400
401 let Some(operator_channel) = operator_channels.get(&operator_id) else {
402 tracing::warn!("received input {id} for unknown operator");
403 continue;
404 };
405 if let Err(err) = operator_channel
406 .send_async(Event::InputClosed {
407 id: input_id.clone(),
408 })
409 .await
410 .wrap_err_with(|| {
411 format!(
412 "failed to send InputClosed({input_id}) to operator `{operator_id}`"
413 )
414 })
415 {
416 tracing::warn!("{err}");
417 }
418
419 if let Some(open_inputs) = open_operator_inputs.get_mut(&operator_id) {
420 open_inputs.remove(&input_id);
421 if open_inputs.is_empty() {
422 tracing::trace!(
424 "all inputs of operator {}/{operator_id} were closed -> closing event channel",
425 node.id()
426 );
427 open_operator_inputs.remove(&operator_id);
428 operator_channels.remove(&operator_id);
429 }
430 }
431 }
432 RuntimeEvent::Event(Event::Error(err)) => eyre::bail!("received error event: {err}"),
433 RuntimeEvent::Event(other) => {
434 tracing::warn!("received unknown event `{other:?}`");
435 }
436 }
437 }
438
439 mem::drop(events);
440
441 Ok(())
442}
443
444async fn forward_stop(
453 operator_channels: &mut HashMap<OperatorId, flume::Sender<Event>>,
454 cause: &StopCause,
455) {
456 tracing::warn!(
457 "runtime received Stop; forwarding to {} operator(s) (dora-rs/dora#2742 diagnostic)",
458 operator_channels.len()
459 );
460 for (id, channel) in operator_channels.drain() {
461 let _ = channel.send_async(Event::Stop(cause.clone())).await;
462 tracing::warn!("forwarded Stop to operator `{id}` (dora-rs/dora#2742 diagnostic)");
463 }
464}
465
466async fn await_send_watching_for_stop<S, T>(
482 send: &mut tokio::task::JoinHandle<T>,
483 events: &mut S,
484 pending: &mut VecDeque<RuntimeEvent>,
485 operator_channels: &mut HashMap<OperatorId, flume::Sender<Event>>,
486) -> Result<T, tokio::task::JoinError>
487where
488 S: Stream<Item = RuntimeEvent> + Unpin,
489{
490 const MAX_BUFFERED: usize = 4;
496
497 let mut stop_seen = false;
498 loop {
499 let watching = !stop_seen && pending.len() < MAX_BUFFERED;
502 tokio::select! {
503 biased;
504 joined = &mut *send => return joined,
505 event = events.next(), if watching => {
506 match event {
507 Some(RuntimeEvent::Event(Event::Stop(cause))) => {
508 forward_stop(operator_channels, &cause).await;
509 stop_seen = true;
510 }
511 Some(other) => pending.push_back(other),
512 None => stop_seen = true,
514 }
515 }
516 }
517 }
518}
519
520fn operator_output_id(operator_id: &OperatorId, output_id: &DataId) -> DataId {
521 DataId::from(format!("{operator_id}/{output_id}"))
522}
523
524#[derive(Debug)]
525enum RuntimeEvent {
526 Operator {
527 id: OperatorId,
528 event: OperatorEvent,
529 },
530 Event(Event),
531}
532
533enum LoopActivity {
541 Idle,
542 Handling { since: Instant, what: &'static str },
543}
544
545fn describe_runtime_event(event: &RuntimeEvent) -> &'static str {
551 match event {
552 RuntimeEvent::Operator { event, .. } => match event {
553 OperatorEvent::Output { .. } => "an operator output",
554 _ => "an operator lifecycle event",
555 },
556 RuntimeEvent::Event(event) => match event {
557 Event::Input { .. } => "an operator input",
558 Event::InputClosed { .. } => "an input-closed event",
559 Event::Stop(_) => "a stop event",
560 Event::Reload { .. } => "a reload event",
561 _ => "an event",
562 },
563 }
564}
565
566fn lock<T>(mutex: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
570 mutex
571 .lock()
572 .unwrap_or_else(std::sync::PoisonError::into_inner)
573}
574
575#[cfg(test)]
576mod stop_responsiveness_tests {
577 use super::*;
578 use futures::stream;
579
580 fn operator_channel() -> (
581 HashMap<OperatorId, flume::Sender<Event>>,
582 flume::Receiver<Event>,
583 ) {
584 let (tx, rx) = flume::unbounded();
585 let mut channels = HashMap::new();
586 channels.insert(OperatorId::from("op".to_string()), tx);
587 (channels, rx)
588 }
589
590 #[tokio::test]
593 async fn a_send_that_completes_is_returned() {
594 let mut send = tokio::spawn(async { 42 });
595 let mut events = stream::empty::<RuntimeEvent>();
596 let mut pending = VecDeque::new();
597 let (mut channels, _rx) = operator_channel();
598
599 let joined =
600 await_send_watching_for_stop(&mut send, &mut events, &mut pending, &mut channels)
601 .await
602 .expect("joined");
603
604 assert_eq!(joined, 42);
605 assert!(pending.is_empty());
606 }
607
608 #[tokio::test]
612 async fn stop_reaches_the_operator_while_a_send_is_in_flight() {
613 let (unblock_tx, unblock_rx) = tokio::sync::oneshot::channel::<()>();
614 let mut send = tokio::spawn(async move {
615 let _ = unblock_rx.await;
616 7
617 });
618 let mut events = stream::iter(vec![
619 RuntimeEvent::Event(Event::Reload { operator_id: None }),
620 RuntimeEvent::Event(Event::Stop(StopCause::Manual)),
621 ]);
622 let mut pending = VecDeque::new();
623 let (mut channels, rx) = operator_channel();
624
625 tokio::spawn(async move {
628 while rx.is_empty() {
629 tokio::task::yield_now().await;
630 }
631 assert!(matches!(rx.recv_async().await, Ok(Event::Stop(_))));
632 let _ = unblock_tx.send(());
633 });
634
635 let joined = tokio::time::timeout(
636 Duration::from_secs(5),
637 await_send_watching_for_stop(&mut send, &mut events, &mut pending, &mut channels),
638 )
639 .await
640 .expect("must not hang")
641 .expect("joined");
642
643 assert_eq!(joined, 7);
644 assert_eq!(
645 pending.len(),
646 1,
647 "the non-Stop event must be kept, not lost"
648 );
649 assert!(
650 channels.is_empty(),
651 "forwarding Stop closes the operator channels"
652 );
653 }
654
655 #[tokio::test]
658 async fn buffering_during_a_send_is_bounded() {
659 let (unblock_tx, unblock_rx) = tokio::sync::oneshot::channel::<()>();
660 let mut send = tokio::spawn(async move {
661 let _ = unblock_rx.await;
662 0
663 });
664 let flood = (0..1000)
665 .map(|_| RuntimeEvent::Event(Event::Reload { operator_id: None }))
666 .collect::<Vec<_>>();
667 let mut events = stream::iter(flood);
668 let mut pending = VecDeque::new();
669 let (mut channels, _rx) = operator_channel();
670
671 tokio::spawn(async move {
672 tokio::time::sleep(Duration::from_millis(50)).await;
673 let _ = unblock_tx.send(());
674 });
675
676 await_send_watching_for_stop(&mut send, &mut events, &mut pending, &mut channels)
677 .await
678 .expect("joined");
679
680 assert!(
681 pending.len() <= 4,
682 "buffered {} events, expected the cap to hold",
683 pending.len()
684 );
685 }
686}