Skip to main content

krabka_client_streams/processor/
erased.rs

1//! Type-erased records, plus the per-dispatch context that the graph driver
2//! hands to each node.
3//!
4//! Records flow between nodes erased, as `Box<dyn Any + Send>`. Only the source
5//! and sink boundaries serialize and deserialize.
6
7use std::{any::Any, collections::VecDeque};
8
9use bytes::Bytes;
10
11use super::record::RecordContext;
12
13/// An error raised while a node processes a record, such as an internal downcast
14/// mismatch. This is unreachable in practice, because `build()` validates the
15/// wiring.
16#[derive(Debug, thiserror::Error)]
17pub enum ProcessorError {
18    #[error("type mismatch in node `{node}`: expected {expected}, found a different type")]
19    Downcast {
20        node: String,
21        expected: &'static str,
22    },
23    #[error("serialization error in sink `{node}`: {message}")]
24    Serde { node: String, message: String },
25    /// A subtopology id the built topology does not have.
26    #[error("the topology has no subtopology `{id}`")]
27    UnknownSubtopology { id: String },
28}
29
30/// A record with erased key/value, as it flows between nodes.
31pub(crate) struct ErasedRecord {
32    pub key: Option<Box<dyn Any + Send>>,
33    pub value: Box<dyn Any + Send>,
34    pub timestamp: i64,
35}
36
37impl ErasedRecord {
38    pub fn new(
39        key: Option<Box<dyn Any + Send>>,
40        value: Box<dyn Any + Send>,
41        timestamp: i64,
42    ) -> Self {
43        Self {
44            key,
45            value,
46            timestamp,
47        }
48    }
49}
50
51/// A record that a sink node emits and the driver collects. The test driver
52/// collects it into an output queue, the runtime hands it to the producer, and
53/// an [`EmbeddedTask`](crate::EmbeddedTask) returns it to the embedder.
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct OutputRecord {
56    /// The sink topic, which is a repartition topic for an internal sink.
57    pub topic: String,
58    /// The serialized key, or `None` for a record without one.
59    pub key: Option<Bytes>,
60    /// The serialized value, or `None` for a tombstone.
61    pub value: Option<Bytes>,
62    /// The record timestamp in epoch milliseconds.
63    pub timestamp: i64,
64}
65
66/// What the driver lends to a node for one `process` call: the forward buffer
67/// for the source and processor children, this node's child indices, the sink
68/// output collector, the source-record context, and the per-task store registry
69/// that `get_state_store` reads.
70pub(crate) struct Dispatch<'a> {
71    pub buffer: &'a mut VecDeque<(usize, ErasedRecord)>,
72    pub children: &'a [usize],
73    pub output: &'a mut Vec<OutputRecord>,
74    pub record_ctx: &'a RecordContext,
75    pub stores: &'a mut crate::store::registry::StoreRegistry,
76    /// The app-wide, fully-replicated global stores, shared across tasks.
77    /// Stream-globaltable join processors read them with
78    /// `ProcessorContext::global_get`.
79    pub globals: &'a crate::runtime::global::GlobalStateManager,
80    /// The graph node this dispatch is positioned at. `schedule` therefore tags
81    /// the owning node, and a punctuator forwards to this node's children.
82    pub node_idx: usize,
83    /// Sink for `ProcessorContext::schedule`: the newly-registered punctuation
84    /// schedules.
85    pub schedules: &'a mut Vec<crate::processor::punctuation::ScheduleEntry>,
86    /// The current stream-time or wall-clock time. This is the BASE that
87    /// `schedule` stamps a new entry's first-fire time from, as
88    /// `base + interval`.
89    pub sched_stream_time: i64,
90    pub sched_wall_clock: i64,
91}
92
93#[cfg(test)]
94mod tests {
95    use assert2::check;
96
97    use super::*;
98
99    #[test]
100    fn erase_then_downcast_roundtrips_value_and_key() {
101        let er = ErasedRecord::new(Some(Box::new(7i32)), Box::new("v".to_string()), 1);
102        let key = er.key.unwrap().downcast::<i32>().unwrap();
103        let val = er.value.downcast::<String>().unwrap();
104        check!(*key == 7);
105        check!(*val == "v");
106    }
107}