1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350
//
// Copyright (c) 2021 - 2024 ZettaScale Technology
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
//
// Contributors:
// ZettaScale Zenoh Team, <zenoh@zettascale.tech>
//
use crate::messages::{Data, DeserializerFn, LinkMessage};
use std::collections::HashMap;
use std::ops::Deref;
use std::sync::Arc;
use anyhow::{anyhow, bail};
use flume::TryRecvError;
use uhlc::Timestamp;
use zenoh_flow_commons::{PortId, Result};
/// The `Inputs` structure contains all the inputs created for a [Sink](crate::prelude::Sink) or an
/// [Operator](crate::prelude::Operator).
///
/// Each input is indexed by its **port identifier**: the name that was indicated in the descriptor
/// of the node. These names are _case sensitive_ and should be an exact match to what was written
/// in the descriptor.
///
/// Zenoh-Flow provides two flavours of input: [InputRaw] and [`Input<T>`]. An [`Input<T>`]
/// conveniently exposes instances of `T` while an [InputRaw] exposes messages, allowing to
/// disregard the contained data.
///
/// The main way to interact with `Inputs` is through the `take` method.
///
/// # Example
///
/// ```no_run
/// # use zenoh_flow_nodes::prelude::*;
/// # let mut inputs = Inputs::default();
/// let input_raw = inputs.take("test raw")
/// .expect("No input name 'test raw' found")
/// .raw();
///
/// let input: Input<u64> = inputs.take("test typed")
/// .expect("No input name 'test typed' found")
/// .typed(
/// |bytes| serde_json::from_slice(bytes).map_err(|e| anyhow!(e))
/// );
/// ```
#[derive(Default)]
pub struct Inputs {
pub(crate) hmap: HashMap<PortId, flume::Receiver<LinkMessage>>,
}
// Dereferencing on the internal `HashMap` allows users to call all the methods implemented on it: `keys()` for one.
impl Deref for Inputs {
type Target = HashMap<PortId, flume::Receiver<LinkMessage>>;
fn deref(&self) -> &Self::Target {
&self.hmap
}
}
impl Inputs {
/// Insert the `flume::Receiver` in the [Inputs], creating the entry if needed in the internal `HashMap`.
pub fn insert(&mut self, port_id: PortId, rx: flume::Receiver<LinkMessage>) {
self.hmap.entry(port_id).or_insert(rx);
}
/// Returns an Input builder for the provided `port_id`, if an input was declared with this exact name in the
/// descriptor of the node, otherwise returns `None`.
///
/// # Usage
///
/// This builder can either produce a, typed, [`Input<T>`](Input) or an [InputRaw]. The main difference between both
/// is the type of data they expose: an [`Input<T>`](Input) automatically tries to downcast or deserialize the data
/// contained in the message to expose `&T`, while an [InputRaw] simply exposes a [LinkMessage].
///
/// ## Typed
///
/// To obtain an [`Input<T>`](Input) one must call `typed` and provide a deserialiser function. In the example below
/// we rely on the `serde_json` crate to do the deserialisation.
///
/// ```no_run
/// # use zenoh_flow_nodes::prelude::*;
/// # let mut inputs = Inputs::default();
/// let input: Input<u64> = inputs.take("test typed")
/// .expect("No input name 'test typed' found")
/// .typed(
/// |bytes| serde_json::from_slice(bytes).map_err(|e| anyhow!(e))
/// );
/// ```
///
/// ## Raw
///
/// To obtain an [InputRaw] one must call `raw`.
///
/// ```no_run
/// # use zenoh_flow_nodes::prelude::*;
/// # let mut inputs = Inputs::default();
/// let input_raw = inputs.take("test raw")
/// .expect("No input name 'test raw' found")
/// .raw();
/// ```
pub fn take(&mut self, port_id: impl AsRef<str>) -> Option<InputBuilder> {
self.hmap
.remove(&port_id.as_ref().into())
.map(|receiver| InputBuilder {
port_id: port_id.as_ref().into(),
receiver,
})
}
}
/// An `InputBuilder` is the intermediate structure to obtain either an [`Input<T>`](Input) or an [InputRaw].
///
/// The main difference between both is the type of data they expose: an [`Input<T>`] automatically
/// tries to downcast or deserialize the data contained in the message to expose `&T`, while an
/// [InputRaw] simply exposes a [LinkMessage].
///
/// # Planned evolution
///
/// Zenoh-Flow will allow tweaking the behaviour of the underlying channels. For now, the
/// `receivers` channels are _unbounded_ and do not implement a dropping policy, which could lead to
/// issues.
pub struct InputBuilder {
pub(crate) port_id: PortId,
pub(crate) receiver: flume::Receiver<LinkMessage>,
}
impl InputBuilder {
/// Consume the `InputBuilder` to produce an [InputRaw].
///
/// An [InputRaw] exposes the [LinkMessage] it receives, without trying to perform any
/// conversion on the data.
///
/// The [InputRaw] was designed for use cases such as load-balancing or rate-limiting. In these
/// scenarios, the node does not need to access the underlying data.
///
/// # `InputRaw` vs `Input<T>`
///
/// If the node needs access to the data to perform computations, an [`Input<T>`] should be
/// favoured as it performs the conversion automatically.
///
/// # Example
///
/// ```no_run
/// # use zenoh_flow_nodes::prelude::*;
/// # let mut inputs = Inputs::default();
/// let input_raw = inputs.take("test raw")
/// .expect("No input name 'test raw' found")
/// .raw();
/// ```
pub fn raw(self) -> InputRaw {
InputRaw {
port_id: self.port_id,
receiver: self.receiver,
}
}
/// Consume the `InputBuilder` to produce an [`Input<T>`].
///
/// An [`Input<T>`] tries to automatically convert the data contained in the [LinkMessage] in
/// order to expose `&T`. Depending on if the data is received serialised or not, to perform
/// this conversion either the `deserialiser` is called or a downcast is attempted.
///
/// # `Input<T>` vs `InputRaw`
///
/// If the node does need to access the data contained in the [LinkMessage], an [InputRaw]
/// should be favoured as it does not try to perform the extra conversion steps.
///
/// # Example
///
/// ```no_run
/// # use zenoh_flow_nodes::prelude::*;
/// # let mut inputs = Inputs::default();
/// let input: Input<u64> = inputs.take("test typed")
/// .expect("No input name 'test typed' found")
/// .typed(
/// |bytes| serde_json::from_slice(bytes).map_err(|e| anyhow!(e))
/// );
/// ```
pub fn typed<T>(
self,
deserializer: impl Fn(&[u8]) -> anyhow::Result<T> + Send + Sync + 'static,
) -> Input<T> {
Input {
input_raw: self.raw(),
deserializer: Arc::new(deserializer),
}
}
}
/// An `InputRaw` receives "raw" [LinkMessage].
///
/// As opposed to a typed [`Input<T>`](Input), an `InputRaw` will not perform any operation on the data it receives.
/// This behaviour is useful when access to the underlying data is either irrelevant (e.g. for rate-limiting purposes)
/// or when Zenoh-Flow should not attempt to interpret the contained [Payload](crate::prelude::Payload) (e.g. for
/// bindings).
#[derive(Clone, Debug)]
pub struct InputRaw {
pub(crate) port_id: PortId,
pub(crate) receiver: flume::Receiver<LinkMessage>,
}
impl InputRaw {
pub fn port_id(&self) -> &PortId {
&self.port_id
}
/// Returns the number of channels associated with this Input.
pub fn channels_count(&self) -> usize {
self.receiver.len()
}
/// Returns the first queued [LinkMessage] or [None] if there is no queued message.
///
/// # Asynchronous alternative: `recv`
///
/// This method is a synchronous fail-fast alternative to it's asynchronous counterpart: `recv`. Although
/// synchronous, this method will not block the thread on which it is executed.
///
/// # Errors
///
/// An error is returned if the associated channel is disconnected.
pub fn try_recv(&self) -> Result<Option<LinkMessage>> {
match self.receiver.try_recv() {
Ok(message) => Ok(Some(message)),
Err(e) => match e {
TryRecvError::Empty => Ok(None),
TryRecvError::Disconnected => {
tracing::error!("Link disconnected: {}", self.port_id);
bail!("Disconnected");
}
},
}
}
/// Returns the first [LinkMessage] that was received, *asynchronously*, on any of the channels associated with this
/// Input.
///
/// If several [LinkMessage] are received at the same time, one is *randomly* selected.
///
/// # Errors
///
/// An error is returned if a channel was disconnected.
pub async fn recv(&self) -> Result<LinkMessage> {
self.receiver.recv_async().await.map_err(|_| {
tracing::error!("Link disconnected: {}", self.port_id);
anyhow!("Disconnected")
})
}
}
/// A typed `Input` receiving [`Data<T>`](Data).
///
/// An `Input` will automatically try to downcast or deserialise the [Payload](crate::prelude::Payload) it receives,
/// exposing a [`Data<T>`](Data).
///
/// The type of conversion performed depends on whether the upstream node resides on the same Zenoh-Flow runtime
/// (downcast) or on another runtime (deserialisation).
///
/// # Performance
///
/// If the data is received serialised from the upstream node, an allocation is performed to host the deserialised `T`.
pub struct Input<T> {
pub(crate) input_raw: InputRaw,
pub(crate) deserializer: Arc<DeserializerFn<T>>,
}
// Dereferencing to the [InputRaw] allows to directly call methods on it with a typed [Input].
impl<T: Send + Sync + 'static> Deref for Input<T> {
type Target = InputRaw;
fn deref(&self) -> &Self::Target {
&self.input_raw
}
}
impl<T: Send + Sync + 'static> Input<T> {
/// Returns the first [`Data<T>`](Data) that was received, *asynchronously*, on any of the channels
/// associated with this Input.
///
/// If several [`Data<T>`](Data) are received at the same time, one is *randomly* selected.
///
/// This method interprets the data to the type associated with this [`Input<T>`](Input).
///
/// # Performance
///
/// As this method interprets the data received, additional operations are performed:
/// - data received serialised is deserialised (an allocation is performed to store an instance of `T`),
/// - data received "typed" are checked against the type associated to this [`Input<T>`](Input).
///
/// # Synchronous alternative: `try_recv`
///
/// This method is an asynchronous alternative to it's synchronous fail-fast counterpart: `try_recv`.
///
/// # Errors
///
/// Several errors can occur:
/// - a channel was disconnected,
/// - Zenoh-Flow failed at interpreting the received data as an instance of `T`.
pub async fn recv(&self) -> Result<(Data<T>, Timestamp)> {
let LinkMessage { payload, timestamp } = self.input_raw.recv().await?;
Ok((
Data::try_from_payload(payload, self.deserializer.clone())?,
timestamp,
))
}
/// Returns the first [`Data<T>`](Data) that was received on any of the channels associated with this Input,
/// or [None] if all the channels are empty.
///
/// # Performance
///
/// As this method interprets the data received, additional operations are performed:
/// - data received serialised is deserialised (an allocation is performed to store an instance of `T`),
/// - data received "typed" are checked against the type associated to this [`Input<T>`](Input).
///
/// # Asynchronous alternative: `recv`
///
/// This method is a synchronous fail-fast alternative to it's asynchronous counterpart: `recv`. Although
/// synchronous, this method will not block the thread on which it is executed.
///
/// # Errors
///
/// Several errors can occur:
/// - a channel was disconnected,
/// - Zenoh-Flow failed at interpreting the received data as an instance of `T`.
///
/// Note that if some channels are disconnected, for each of such channel an error is logged.
pub fn try_recv(&self) -> Result<Option<(Data<T>, Timestamp)>> {
if let Some(LinkMessage { payload, timestamp }) = self.input_raw.try_recv()? {
return Ok(Some((
Data::try_from_payload(payload, self.deserializer.clone())?,
timestamp,
)));
}
Ok(None)
}
}
#[cfg(test)]
#[path = "./tests/input-tests.rs"]
mod tests;