use crate::Interests;
use crate::control::{NodeControlMsg, RuntimeCtrlMsgSender};
use crate::effect_handler::{
EffectHandlerCore, SourceTagging, TelemetryTimerCancelHandle, TimerCancelHandle,
};
use crate::error::{Error, TypedError};
use crate::message::Sender;
use crate::node::NodeId;
use crate::output_router::OutputRouter;
use crate::runtime_services::{CodecEffectHandler, PipelineRuntimeServices};
use crate::terminal_state::TerminalState;
use async_trait::async_trait;
use otel_arrow_dfe_channel::error::RecvError;
use otel_arrow_dfe_config::PortName;
use otel_arrow_dfe_config::authorized_identity_policy::AuthorizedIdentityPolicy;
use otel_arrow_dfe_config::transport_headers_policy::CompiledHeaderCapturePolicy;
use otel_arrow_dfe_pdata_codec::CodecService;
use otel_arrow_dfe_telemetry::error::Error as TelemetryError;
use otel_arrow_dfe_telemetry::metrics::{MetricSet, MetricSetHandler};
use otel_arrow_dfe_telemetry::reporter::MetricsReporter;
use std::collections::HashMap;
use std::net::SocketAddr;
use std::rc::Rc;
use std::time::Duration;
use tokio::net::{TcpListener, UdpSocket};
#[async_trait( ? Send)]
pub trait Receiver<PData> {
async fn start(
self: Box<Self>,
ctrl_chan: ControlChannel<PData>,
effect_handler: EffectHandler<PData>,
) -> Result<TerminalState, Error>;
}
pub struct ControlChannel<PData> {
rx: crate::message::Receiver<NodeControlMsg<PData>>,
}
impl<PData> ControlChannel<PData> {
#[must_use]
pub fn new(rx: crate::message::Receiver<NodeControlMsg<PData>>) -> Self {
Self { rx }
}
pub async fn recv(&mut self) -> Result<NodeControlMsg<PData>, RecvError> {
self.rx.recv().await
}
}
#[derive(Clone)]
pub struct EffectHandler<PData> {
pub(crate) core: EffectHandlerCore<PData>,
pub router: OutputRouter<Sender<PData>>,
capture_policy: Option<Rc<CompiledHeaderCapturePolicy>>,
authorized_identity_policy: Option<Rc<AuthorizedIdentityPolicy>>,
}
impl<PData> EffectHandler<PData> {
#[must_use]
pub fn new(
node_id: NodeId,
msg_senders: HashMap<PortName, Sender<PData>>,
default_port: Option<PortName>,
node_request_sender: RuntimeCtrlMsgSender<PData>,
metrics_reporter: MetricsReporter,
runtime_services: PipelineRuntimeServices,
) -> Self {
let mut core = EffectHandlerCore::new(node_id.clone(), metrics_reporter, runtime_services);
core.set_runtime_ctrl_msg_sender(node_request_sender);
let router = OutputRouter::new(node_id, msg_senders, default_port);
EffectHandler {
core,
router,
capture_policy: None,
authorized_identity_policy: None,
}
}
#[must_use]
pub fn receiver_id(&self) -> NodeId {
self.core.node_id()
}
pub fn set_source_tagging(&mut self, value: SourceTagging) {
self.core.set_source_tagging(value);
}
#[must_use]
pub const fn source_tagging(&self) -> SourceTagging {
self.core.source_tagging()
}
#[must_use]
pub fn connected_ports(&self) -> Vec<PortName> {
self.router.connected_ports()
}
#[must_use]
pub fn node_interests(&self) -> Interests {
self.core.node_interests()
}
#[must_use]
pub fn capture_policy(&self) -> Option<&CompiledHeaderCapturePolicy> {
self.capture_policy.as_deref()
}
pub fn set_capture_policy(&mut self, policy: Option<CompiledHeaderCapturePolicy>) {
self.capture_policy = policy.map(Rc::new);
}
#[must_use]
pub fn authorized_identity_policy(&self) -> Option<&AuthorizedIdentityPolicy> {
self.authorized_identity_policy.as_deref()
}
pub fn set_authorized_identity_policy(&mut self, policy: Option<AuthorizedIdentityPolicy>) {
self.authorized_identity_policy = policy.map(Rc::new);
}
#[inline]
pub async fn send_message(&self, data: PData) -> Result<(), TypedError<PData>> {
self.router.send_default(data).await
}
#[inline]
pub fn try_send_message(&self, data: PData) -> Result<(), TypedError<PData>> {
self.router.try_send_default(data)
}
#[inline]
pub async fn send_message_to<P>(&self, port: P, data: PData) -> Result<(), TypedError<PData>>
where
P: Into<PortName>,
{
self.router.send_to(port, data).await
}
#[inline]
pub fn try_send_message_to<P>(&self, port: P, data: PData) -> Result<(), TypedError<PData>>
where
P: Into<PortName>,
{
self.router.try_send_to(port, data)
}
pub fn tcp_listener(&self, addr: SocketAddr) -> Result<TcpListener, Error> {
self.core.tcp_listener(addr, self.receiver_id())
}
pub fn udp_socket(&self, addr: SocketAddr) -> Result<UdpSocket, Error> {
self.core.udp_socket(addr, self.receiver_id())
}
pub async fn info(&self, message: &str) {
self.core.info(message).await;
}
pub async fn start_periodic_timer(
&self,
duration: Duration,
) -> Result<TimerCancelHandle<PData>, Error> {
self.core.start_periodic_timer(duration).await
}
pub async fn start_periodic_telemetry(
&self,
duration: Duration,
) -> Result<TelemetryTimerCancelHandle<PData>, Error> {
self.core.start_periodic_telemetry(duration).await
}
pub async fn notify_receiver_drained(&self) -> Result<(), Error> {
self.core.notify_receiver_drained().await
}
#[allow(dead_code)] pub(crate) fn report_metrics<M: MetricSetHandler + 'static>(
&mut self,
metrics: &mut MetricSet<M>,
) -> Result<(), TelemetryError> {
self.core.report_metrics(metrics)
}
}
impl<PData> CodecEffectHandler for EffectHandler<PData> {
fn codec_service(&self) -> &CodecService {
self.core.runtime_services.codecs()
}
}
#[cfg(test)]
mod tests {
#![allow(missing_docs)]
use super::*;
use crate::control::runtime_ctrl_msg_channel;
use crate::local::message::LocalSender;
use crate::testing::test_node;
use otel_arrow_dfe_channel::error::SendError;
use otel_arrow_dfe_channel::mpsc;
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
use tokio::time::{Duration, timeout};
fn channel<T>(capacity: usize) -> (mpsc::Sender<T>, mpsc::Receiver<T>) {
mpsc::Channel::new(capacity)
}
#[tokio::test]
async fn effect_handler_send_message_to_named_port() {
let (a_tx, a_rx) = channel::<u64>(10);
let (b_tx, b_rx) = channel::<u64>(10);
let mut senders = HashMap::new();
let _ = senders.insert("a".into(), Sender::Local(LocalSender::mpsc(a_tx)));
let _ = senders.insert("b".into(), Sender::Local(LocalSender::mpsc(b_tx)));
let (ctrl_tx, _ctrl_rx) = runtime_ctrl_msg_channel(4);
let (_metrics_rx, metrics_reporter) = MetricsReporter::create_new_and_receiver(1);
let eh = EffectHandler::new(
test_node("recv"),
senders,
None,
ctrl_tx,
metrics_reporter,
crate::testing::test_pipeline_runtime_services(),
);
eh.send_message_to("b", 42).await.unwrap();
assert!(
timeout(Duration::from_millis(50), a_rx.recv())
.await
.is_err()
);
assert_eq!(b_rx.recv().await.unwrap(), 42);
}
#[tokio::test]
async fn effect_handler_send_message_single_port_fallback() {
let (tx, rx) = channel::<u64>(10);
let mut senders = HashMap::new();
let _ = senders.insert("only".into(), Sender::Local(LocalSender::mpsc(tx)));
let (ctrl_tx, _ctrl_rx) = runtime_ctrl_msg_channel(4);
let (_metrics_rx, metrics_reporter) = MetricsReporter::create_new_and_receiver(1);
let eh = EffectHandler::new(
test_node("recv"),
senders,
None,
ctrl_tx,
metrics_reporter,
crate::testing::test_pipeline_runtime_services(),
);
eh.send_message(7).await.unwrap();
assert_eq!(rx.recv().await.unwrap(), 7);
}
#[tokio::test]
async fn effect_handler_send_message_uses_default_port() {
let (a_tx, a_rx) = channel::<u64>(10);
let (b_tx, b_rx) = channel::<u64>(10);
let mut senders = HashMap::new();
let _ = senders.insert("a".into(), Sender::Local(LocalSender::mpsc(a_tx)));
let _ = senders.insert("b".into(), Sender::Local(LocalSender::mpsc(b_tx)));
let (ctrl_tx, _ctrl_rx) = runtime_ctrl_msg_channel(4);
let (_metrics_rx, metrics_reporter) = MetricsReporter::create_new_and_receiver(1);
let eh = EffectHandler::new(
test_node("recv"),
senders,
Some("a".into()),
ctrl_tx,
metrics_reporter,
crate::testing::test_pipeline_runtime_services(),
);
eh.send_message(11).await.unwrap();
assert_eq!(a_rx.recv().await.unwrap(), 11);
assert!(
timeout(Duration::from_millis(50), b_rx.recv())
.await
.is_err()
);
}
#[tokio::test]
async fn effect_handler_send_message_ambiguous_without_default() {
let (a_tx, a_rx) = channel::<u64>(10);
let (b_tx, b_rx) = channel::<u64>(10);
let mut senders = HashMap::new();
let _ = senders.insert("a".into(), Sender::Local(LocalSender::mpsc(a_tx)));
let _ = senders.insert("b".into(), Sender::Local(LocalSender::mpsc(b_tx)));
let (ctrl_tx, _ctrl_rx) = runtime_ctrl_msg_channel(4);
let (_metrics_rx, metrics_reporter) = MetricsReporter::create_new_and_receiver(1);
let eh = EffectHandler::new(
test_node("recv"),
senders,
None,
ctrl_tx,
metrics_reporter,
crate::testing::test_pipeline_runtime_services(),
);
let res = eh.send_message(5).await;
assert!(res.is_err());
assert!(
timeout(Duration::from_millis(50), a_rx.recv())
.await
.is_err()
);
assert!(
timeout(Duration::from_millis(50), b_rx.recv())
.await
.is_err()
);
}
#[tokio::test]
async fn effect_handler_connected_ports_lists_all() {
let (a_tx, _a_rx) = channel::<u64>(1);
let (b_tx, _b_rx) = channel::<u64>(1);
let mut senders = HashMap::new();
let _ = senders.insert("a".into(), Sender::Local(LocalSender::mpsc(a_tx)));
let _ = senders.insert("b".into(), Sender::Local(LocalSender::mpsc(b_tx)));
let (ctrl_tx, _ctrl_rx) = runtime_ctrl_msg_channel(4);
let (_metrics_rx, metrics_reporter) = MetricsReporter::create_new_and_receiver(1);
let eh = EffectHandler::new(
test_node("recv"),
senders,
None,
ctrl_tx,
metrics_reporter,
crate::testing::test_pipeline_runtime_services(),
);
let ports: HashSet<_> = eh.connected_ports().into_iter().collect();
let expected: HashSet<_> = [Cow::from("a"), Cow::from("b")].into_iter().collect();
assert_eq!(ports, expected);
}
#[test]
fn effect_handler_try_send_message_success() {
let (tx, rx) = channel::<u64>(10);
let mut senders = HashMap::new();
let _ = senders.insert("out".into(), Sender::Local(LocalSender::mpsc(tx)));
let (ctrl_tx, _ctrl_rx) = runtime_ctrl_msg_channel(4);
let (_metrics_rx, metrics_reporter) = MetricsReporter::create_new_and_receiver(1);
let eh = EffectHandler::new(
test_node("recv"),
senders,
Some("out".into()),
ctrl_tx,
metrics_reporter,
crate::testing::test_pipeline_runtime_services(),
);
assert!(eh.try_send_message(42).is_ok());
assert_eq!(rx.try_recv().unwrap(), 42);
}
#[test]
fn effect_handler_try_send_message_inbox_full() {
let (tx, _rx) = channel::<u64>(1);
let mut senders = HashMap::new();
let _ = senders.insert("out".into(), Sender::Local(LocalSender::mpsc(tx)));
let (ctrl_tx, _ctrl_rx) = runtime_ctrl_msg_channel(4);
let (_metrics_rx, metrics_reporter) = MetricsReporter::create_new_and_receiver(1);
let eh = EffectHandler::new(
test_node("recv"),
senders,
Some("out".into()),
ctrl_tx,
metrics_reporter,
crate::testing::test_pipeline_runtime_services(),
);
assert!(eh.try_send_message(1).is_ok());
let result = eh.try_send_message(2);
assert!(matches!(
result,
Err(TypedError::ChannelSendError(SendError::Full(2)))
));
}
#[test]
fn effect_handler_try_send_message_no_default_sender() {
let (a_tx, _a_rx) = channel::<u64>(10);
let (b_tx, _b_rx) = channel::<u64>(10);
let mut senders = HashMap::new();
let _ = senders.insert("a".into(), Sender::Local(LocalSender::mpsc(a_tx)));
let _ = senders.insert("b".into(), Sender::Local(LocalSender::mpsc(b_tx)));
let (ctrl_tx, _ctrl_rx) = runtime_ctrl_msg_channel(4);
let (_metrics_rx, metrics_reporter) = MetricsReporter::create_new_and_receiver(1);
let eh = EffectHandler::new(
test_node("recv"),
senders,
None,
ctrl_tx,
metrics_reporter,
crate::testing::test_pipeline_runtime_services(),
);
let result = eh.try_send_message(99);
assert!(matches!(result, Err(TypedError::Error(_))));
}
#[test]
fn effect_handler_try_send_message_to_success() {
let (a_tx, a_rx) = channel::<u64>(10);
let (b_tx, b_rx) = channel::<u64>(10);
let mut senders = HashMap::new();
let _ = senders.insert("a".into(), Sender::Local(LocalSender::mpsc(a_tx)));
let _ = senders.insert("b".into(), Sender::Local(LocalSender::mpsc(b_tx)));
let (ctrl_tx, _ctrl_rx) = runtime_ctrl_msg_channel(4);
let (_metrics_rx, metrics_reporter) = MetricsReporter::create_new_and_receiver(1);
let eh = EffectHandler::new(
test_node("recv"),
senders,
None,
ctrl_tx,
metrics_reporter,
crate::testing::test_pipeline_runtime_services(),
);
assert!(eh.try_send_message_to("b", 42).is_ok());
assert_eq!(b_rx.try_recv().unwrap(), 42);
assert!(a_rx.try_recv().is_err());
}
#[test]
fn effect_handler_try_send_message_to_channel_full() {
let (tx, _rx) = channel::<u64>(1);
let mut senders = HashMap::new();
let _ = senders.insert("out".into(), Sender::Local(LocalSender::mpsc(tx)));
let (ctrl_tx, _ctrl_rx) = runtime_ctrl_msg_channel(4);
let (_metrics_rx, metrics_reporter) = MetricsReporter::create_new_and_receiver(1);
let eh = EffectHandler::new(
test_node("recv"),
senders,
None,
ctrl_tx,
metrics_reporter,
crate::testing::test_pipeline_runtime_services(),
);
assert!(eh.try_send_message_to("out", 1).is_ok());
let result = eh.try_send_message_to("out", 2);
assert!(matches!(
result,
Err(TypedError::ChannelSendError(SendError::Full(2)))
));
}
#[test]
fn effect_handler_try_send_message_to_unknown_port() {
let (tx, _rx) = channel::<u64>(10);
let mut senders = HashMap::new();
let _ = senders.insert("out".into(), Sender::Local(LocalSender::mpsc(tx)));
let (ctrl_tx, _ctrl_rx) = runtime_ctrl_msg_channel(4);
let (_metrics_rx, metrics_reporter) = MetricsReporter::create_new_and_receiver(1);
let eh = EffectHandler::new(
test_node("recv"),
senders,
None,
ctrl_tx,
metrics_reporter,
crate::testing::test_pipeline_runtime_services(),
);
let result = eh.try_send_message_to("unknown", 99);
assert!(matches!(result, Err(TypedError::Error(_))));
}
}