zenoh_flow/io/
input.rs

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