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;