Skip to main content

dora_node_api/
lib.rs

1//! This crate enables you to create nodes for the [Dora] dataflow framework.
2//!
3//! [Dora]: https://dora-rs.ai/
4//!
5//! ## The Dora Framework
6//!
7//! Dora is a dataflow frame work that models applications as a directed graph, with nodes
8//! representing operations and edges representing data transfer.
9//! The layout of the dataflow graph is defined through a YAML file in Dora.
10//! For details, see our [Dataflow Specification](https://dora-rs.ai/docs/api/dataflow-config/)
11//! chapter.
12//!
13//! Dora nodes are typically spawned by the Dora framework, instead of spawning them manually.
14//! If you want to spawn a node manually, define it as a [_dynamic_ node](#dynamic-nodes).
15//!
16//! ## Normal Usage
17//!
18//! In order to connect your executable to Dora, you need to initialize a [`DoraNode`].
19//! For standard nodes, the recommended initialization function is [`init_from_env`][`DoraNode::init_from_env`].
20//! This function will return two values, a [`DoraNode`] instance and an [`EventStream`]:
21//!
22//! ```no_run
23//! use dora_node_api::DoraNode;
24//!
25//! let (mut node, mut events) = DoraNode::init_from_env()?;
26//! # Ok::<(), eyre::Report>(())
27//! ```
28//!
29//! You can use the `node` instance to send outputs and retrieve information about the node and
30//! the dataflow. The `events` stream yields the inputs that the node defines in the dataflow
31//! YAML file and other incoming events.
32//!
33//! ### Sending Outputs
34//!
35//! The [`DoraNode`] instance enables you to send outputs in different formats.
36//! For best performance, use the [Arrow](https://arrow.apache.org/docs/index.html) data format
37//! and one of the output functions that utilizes shared memory.
38//!
39//! ### Receiving Events
40//!
41//! The [`EventStream`] implements the [`Stream`](futures::Stream) trait, yielding the incoming
42//! [`Event`]s. Iterate it asynchronously with [`StreamExt::next`](futures::StreamExt::next), or use
43//! the synchronous [`recv`](EventStream::recv) loop.
44//!
45//! Nodes should iterate over this event stream and react to events that they are interested in.
46//! Typically, the most important event type is [`Event::Input`].
47//! You don't need to handle all events, it's fine to ignore events that are not relevant to your node.
48//!
49//! The event stream will close itself after a [`Event::Stop`] was received.
50//! A manual `break` on [`Event::Stop`] is typically not needed.
51//! _(You probably do need to use a manual `break` on stop events when using the
52//! [`StreamExt::merge`][`futures_concurrency::stream::StreamExt::merge`] implementation on
53//! [`EventStream`] to combine the stream with an external one.)_
54//!
55//! Once the event stream finished, nodes should exit.
56//! Note that Dora kills nodes that don't exit quickly after a [`Event::Stop`] of type
57//! [`StopCause::Manual`] was received.
58//!
59//!
60//!
61//! ## Dynamic Nodes
62//!
63//! <div class="warning">
64//!
65//! Dynamic nodes have certain [limitations](#limitations). Use with care.
66//!
67//! </div>
68//!
69//! Nodes can be defined as `dynamic` by setting their `path` attribute to `dynamic` in the
70//! dataflow YAML file. Dynamic nodes are not spawned by the Dora framework and need to be started
71//! manually.
72//!
73//! Dynamic nodes cannot use the [`DoraNode::init_from_env`] function for initialization.
74//! Instead, they can be initialized through the [`DoraNode::init_from_node_id`] function.
75//!
76//! ### Limitations
77//!
78//! - Dynamic nodes **don't work with `dora run`**.
79//! - As dynamic nodes are identified by their node ID, this **ID must be unique**
80//!   across all running dataflows.
81//! - For distributed dataflows, nodes need to be manually spawned on the correct machine.
82//!
83//!
84//! ## Node Integration Testing
85//!
86//! Dora provides built-in support for integration testing of nodes. See the [integration_testing]
87//! module for details.
88
89#![warn(missing_docs)]
90
91/// Apache Arrow 59, dora's current **internal** major.
92///
93/// Enabled by the `arrow-v59` feature, which pulls no extra dependency: this
94/// is the same copy of Arrow 59 dora itself links, so
95/// [`DoraArray::as_array`] hands it back for free and arrays built with it
96/// need no conversion.
97///
98/// This is deliberately not `pub use arrow;`. A bare `arrow` re-export changes
99/// meaning silently when dora bumps its internal major; `arrow_v59` either
100/// exists and means Arrow 59, or it is visibly gone. See
101/// `docs/plan-arrow-version-decoupling.md`.
102#[cfg(feature = "arrow-v59")]
103pub use arrow as arrow_v59;
104/// Apache Arrow 58, for nodes that name that major.
105///
106/// Enabled by the `arrow-v58` feature. Arrow 58 is **not** dora's internal
107/// major, so payloads cross a C Data Interface hop, exposed as `TryFrom`
108/// impls in both directions (`DoraArray::try_from(&arr as &dyn arrow58 Array)`
109/// and `arrow_v58::array::ArrayRef::try_from(&dora)`). The hop is fallible —
110/// hence `TryFrom` and not `From` — but does not copy buffers.
111#[cfg(feature = "arrow-v58")]
112pub use arrow58 as arrow_v58;
113// Deliberately *not* a glob re-export: `dora_arrow_convert::internal` is an
114// Arrow-typed, semver-exempt seam for dora's own crates and must not become
115// reachable through the frozen `dora-node-api` surface.
116pub use dora_arrow_convert::{DoraArray, IntoArrow, into_vec};
117pub use dora_core::uhlc;
118
119/// The subset of [`dora_core`] that is part of dora's public API.
120///
121/// Deliberately not `pub use dora_core::self`. Re-exporting the whole crate
122/// froze `dora_core`'s descriptor, topics, build, manifest and type-registry
123/// surface into dora's 1.0 guarantee, none of which a node needs — the only
124/// items reached through this path anywhere are the three id types below and
125/// `uhlc`, which is re-exported at the crate root.
126///
127/// The `config` path is preserved verbatim because
128/// `dora_node_api::dora_core::config::DataId` is what `dora new` scaffolds and
129/// what the Rust API reference documents.
130pub mod dora_core {
131    /// Identifier types used in node and dataflow configuration.
132    pub mod config {
133        pub use dora_core::config::{DataId, NodeId, OperatorId};
134    }
135    pub use dora_core::uhlc;
136}
137pub use dora_message::{
138    DataflowId,
139    metadata::{
140        self, FIN, FLUSH, GOAL_ID, GOAL_STATUS, GOAL_STATUS_ABORTED, GOAL_STATUS_CANCELED,
141        GOAL_STATUS_SUCCEEDED, Metadata, MetadataParameters, OPEN_TELEMETRY_CONTEXT, Parameter,
142        REQUEST_ID, SEGMENT_ID, SEQ, SESSION_ID, get_bool_param, get_integer_param,
143        get_string_param,
144    },
145};
146use dora_message::{
147    common::Timestamped,
148    daemon_to_node::{DaemonCommunication, DaemonReply},
149    node_to_daemon::DaemonRequest,
150};
151pub use event_stream::{
152    Event, EventScheduler, EventStream, StopCause, TryRecvError,
153    input_tracker::{InputState, InputTracker},
154    merged,
155};
156pub use futures;
157#[cfg(feature = "tracing")]
158pub use node::init_tracing;
159pub use node::{
160    DataSample, DoraNode, DoraNodeBuilder, EncodedSample, SampleAllocator, StreamSegment,
161    ZERO_COPY_THRESHOLD, arrow_utils,
162};
163pub use uuid;
164
165pub use serde_json;
166use tokio::sync::oneshot;
167
168mod daemon_connection;
169mod error;
170/// Asynchronous event stream and daemon communication.
171pub mod event_stream;
172pub mod integration_testing;
173mod node;
174mod orphan_guard;
175
176pub use error::{NodeError, NodeResult, PatternError};
177
178/// Backward-compatible alias for [`Event`], so code using the old `DoraEvent`
179/// name still compiles.
180pub use Event as DoraEvent;
181
182#[derive(Debug)]
183enum DaemonCommunicationWrapper {
184    Standard(DaemonCommunication),
185    Testing {
186        channel:
187            tokio::sync::mpsc::Sender<(Timestamped<DaemonRequest>, oneshot::Sender<DaemonReply>)>,
188        /// Shared with the testing daemon so Drop can interrupt a scheduled
189        /// `next_event` sleep (dora-rs/dora#2855).
190        shutdown: std::sync::Arc<std::sync::atomic::AtomicBool>,
191    },
192}
193
194impl From<DaemonCommunication> for DaemonCommunicationWrapper {
195    fn from(value: DaemonCommunication) -> Self {
196        DaemonCommunicationWrapper::Standard(value)
197    }
198}