Skip to main content

sift_stream/stream/
mod.rs

1use crate::stream::run::{RunSelector, load_run_by_form, load_run_by_id};
2use async_trait::async_trait;
3use sift_connect::SiftChannel;
4use sift_error::prelude::*;
5use sift_rs::runs::v2::Run;
6use uuid::Uuid;
7
8use crate::metrics::SiftStreamMetricsSnapshot;
9
10/// Concerned with building and configuring and instance of [SiftStream].
11pub mod builder;
12
13/// Concerned with constructing values for channels/sensors that get telemetered.
14pub mod channel;
15
16/// Shared helper functions used across stream implementations.
17mod helpers;
18
19/// Implementations for different modes of streaming.
20pub mod mode;
21
22/// Concerned with gRPC retries.
23pub mod retry;
24pub use retry::RetryPolicy;
25
26/// Concerned with accessing or creating runs for [SiftStream]
27pub mod run;
28
29/// Concerned with constructing values of time that make up the time-series sent ot Sift.
30pub mod time;
31
32/// Concerned with validating flows and detecting if changes are being made to an ingestion config
33/// in a manner that isn't backwards compatible.
34pub(crate) mod flow;
35
36/// Task-based architecture for non-blocking SiftStream operations
37pub mod tasks;
38
39/// Convenience wrapper that auto-registers flows on first send.
40pub mod auto_register;
41pub use auto_register::{AutoRegisterSendError, AutoRegisterStream, SiftStreamAutoRegister};
42
43/// Error types returned by [`Transport`] send methods.
44pub mod send_error;
45pub use send_error::{SendError, SiftStreamSendError, SiftStreamTrySendError, TrySendError};
46
47#[cfg(test)]
48mod test;
49
50/// Provides a point-in-time snapshot of stream metrics.
51///
52/// Implemented by [`IngestionConfigEncoder`](crate::IngestionConfigEncoder). Snapshots are
53/// non-blocking and do not affect stream operation. Obtain one via
54/// [`SiftStream::get_metrics_snapshot`].
55pub trait MetricsSnapshot: private::Sealed {
56    fn snapshot(&self) -> SiftStreamMetricsSnapshot;
57}
58
59/// Implemented by types that can be encoded and sent via [`SiftStream::send`].
60///
61/// The two concrete implementations are [`Flow`](crate::mode::ingestion_config::Flow) and
62/// [`FlowBuilder`](crate::flow::FlowBuilder). The associated `Encoder` type links each
63/// encodeable to the specific encoder implementation that processes it — external types cannot
64/// implement this trait because `Encoder` is sealed.
65pub trait Encodeable {
66    type Output: Send + Sync;
67    type Encoder: Encoder<Message = Self::Output>;
68
69    fn encode(
70        self,
71        encoder: &mut Self::Encoder,
72        stream_id: &Uuid,
73        run: Option<&Run>,
74    ) -> Option<Self::Output>;
75}
76
77/// A trait that indicates that a type can be encoded by it.
78///
79/// This trait is used to tie an [`Encoder`] to the [`Encodeable`]s that
80/// it can encode.
81pub trait Encoder: private::Sealed {
82    type Message: Send + Sync;
83}
84
85/// Defines how encoded telemetry messages are delivered to their destination.
86///
87/// Three concrete implementations are provided:
88///
89/// - [`LiveStreamingOnly`](crate::LiveStreamingOnly) — delivers messages to Sift in real-time
90///   over a single bounded ingestion channel. No checkpointing, no disk backups.
91/// - [`LiveStreamingWithBackups`](crate::LiveStreamingWithBackups) — delivers messages to Sift
92///   in real-time with periodic checkpointing and disk backups. Uses a dual-channel
93///   architecture; see below.
94/// - [`FileBackup`](crate::FileBackup) — writes messages to rolling disk files without
95///   streaming live to Sift.
96///
97/// ## Send API
98///
99/// Each implementation exposes four send methods that differ in their backpressure behaviour:
100///
101/// | Method | Blocks? | Error on failure |
102/// |---|---|---|
103/// | [`send`](Transport::send) | Yes — awaits until the channel has capacity | [`SendError<T>`] with the undelivered message |
104/// | [`send_requests`](Transport::send_requests) | Yes — per-message backpressure | [`SendError<Vec<T>>`] with all undelivered messages |
105/// | [`try_send`](Transport::try_send) | No — returns immediately | [`TrySendError<T>`] as `Full(T)` or `Closed(T)` |
106/// | [`try_send_requests`](Transport::try_send_requests) | No — fails on first undeliverable message | [`TrySendError<Vec<T>>`] with all undelivered |
107///
108/// In every failure case the undelivered message(s) are returned inside the error variant so
109/// that the caller can decide whether to retry, log, buffer locally, or discard them.
110///
111/// ## Backpressure sources
112///
113/// The channel that applies backpressure to [`send`](Transport::send) differs per mode. Knowing
114/// which channel to tune is important when adjusting capacity via the mode builders:
115///
116/// | Mode | [`send`](Transport::send) awaits on | Capacity setting |
117/// |---|---|---|
118/// | [`LiveStreamingOnly`](crate::LiveStreamingOnly) | ingestion channel | [`ingestion_data_channel_capacity`](crate::LiveOnlyBuilder::ingestion_data_channel_capacity) |
119/// | [`LiveStreamingWithBackups`](crate::LiveStreamingWithBackups) | backup channel only — ingestion uses force-send | [`backup_data_channel_capacity`](crate::LiveWithBackupsBuilder::backup_data_channel_capacity) |
120/// | [`FileBackup`](crate::FileBackup) | write channel | [`backup_data_channel_capacity`](crate::FileBackupBuilder::backup_data_channel_capacity) |
121///
122/// ## Channel semantics for `LiveStreamingWithBackups`
123///
124/// `LiveStreamingWithBackups` maintains two internal bounded channels:
125///
126/// - **backup channel** — the primary durability path. [`send`](Transport::send) awaits here.
127/// - **ingestion channel** — forwards messages to the gRPC task using a *force-send* strategy:
128///   when full, the **oldest buffered message is evicted** to make room for the incoming one.
129///   Evicted messages are redirected to the backup channel.
130///
131/// Because of force-send eviction, the message returned inside an error variant from
132/// [`send`](Transport::send) or [`send_requests`](Transport::send_requests) may be an **older
133/// displaced message**, not necessarily the one you just sent.
134///
135/// This trait is sealed: only implementations within this crate are permitted.
136#[async_trait]
137pub trait Transport: private::Sealed {
138    type Message: Send + Sync;
139    type Encoder: Encoder<Message = Self::Message>;
140
141    /// Send a single message with backpressure.
142    ///
143    /// Awaits until the backing channel has capacity, then delivers the message.
144    ///
145    /// # Errors
146    ///
147    /// Returns [`SendError<Self::Message>`] containing a potentially undelivered message.
148    ///
149    /// Depending on the implementation of [`Transport`], the undelivered message is not
150    /// necessarily the message that was provided to the current invocation of [`Self::send`].
151    ///
152    /// See implementation documentation for details.
153    async fn send(
154        &mut self,
155        stream_id: &Uuid,
156        message: Self::Message,
157    ) -> std::result::Result<(), SendError<Self::Message>>;
158
159    /// Send a batch of messages with backpressure.
160    ///
161    /// Awaits channel capacity for each message in turn. Stops on the first failure and
162    /// returns the failed message together with all remaining (not-yet-attempted) messages.
163    ///
164    /// # Errors
165    ///
166    /// Returns [`SendError<Vec<Self::Message>>`] containing potentially undelivered messages.
167    ///
168    /// Depending on the implementation of [`Transport`], the undelivered messages are not
169    /// necessarily the messages that were provided to the current invocation of [`Self::send_requests`].
170    ///
171    /// See implementation documentation for details.
172    async fn send_requests<I>(
173        &mut self,
174        stream_id: &Uuid,
175        requests: I,
176    ) -> std::result::Result<(), SendError<Vec<Self::Message>>>
177    where
178        I: IntoIterator<Item = Self::Message> + Send,
179        I::IntoIter: Send;
180
181    /// Attempt to send a single message without blocking.
182    ///
183    /// Returns immediately regardless of whether the channel has capacity.
184    ///
185    /// # Errors
186    ///
187    /// Returns [`TrySendError<Self::Message>`] containing a potentially undelivered message:
188    /// - [`TrySendError::Full`] — the channel is at capacity; consider retrying with
189    ///   [`send`](Transport::send) to apply backpressure instead.
190    /// - [`TrySendError::Closed`] — the channel has been closed.
191    ///
192    /// Depending on the implementation of [`Transport`], the undelivered messages are not
193    /// necessarily the messages that were provided to the current invocation of [`Self::try_send`].
194    ///
195    /// See implementation documentation for details.
196    fn try_send(
197        &mut self,
198        stream_id: &Uuid,
199        message: Self::Message,
200    ) -> std::result::Result<(), TrySendError<Self::Message>>;
201
202    /// Attempt to send a batch of messages without blocking.
203    ///
204    /// Calls [`try_send`](Transport::try_send) for each message in turn. Returns immediately
205    /// on the first failure, bundling the failed message with any remaining unprocessed
206    /// messages.
207    ///
208    /// # Errors
209    ///
210    /// Returns [`TrySendError<Vec<Self::Message>>`] containing potentially undelivered messages.
211    /// - [`TrySendError::Full`] — the channel was at capacity for one of the messages.
212    /// - [`TrySendError::Closed`] — the channel was closed.
213    ///
214    /// Depending on the implementation of [`Transport`], the undelivered messages are not
215    /// necessarily the messages that were provided to the current invocation of [`Self::try_send_requests`].
216    ///
217    /// See implementation documentation for details.
218    fn try_send_requests<I>(
219        &mut self,
220        stream_id: &Uuid,
221        requests: I,
222    ) -> std::result::Result<(), TrySendError<Vec<Self::Message>>>
223    where
224        I: IntoIterator<Item = Self::Message> + Send,
225        I::IntoIter: Send;
226
227    /// Flush any remaining messages and cleanly shut down the transport.
228    ///
229    /// Must be called when ingestion is complete. Dropping a [`SiftStream`] without
230    /// calling `finish` may result in tail-end data not reaching Sift.
231    async fn finish(self, stream_id: &Uuid) -> Result<()>;
232}
233
234/// Generic wrapper over a telemetry transport that provides a consistent send API regardless
235/// of the underlying mode.
236///
237/// `E` is the encoder (e.g. [`IngestionConfigEncoder`](crate::IngestionConfigEncoder)) and `T`
238/// is the transport (e.g. [`LiveStreamingOnly`](crate::LiveStreamingOnly),
239/// [`LiveStreamingWithBackups`](crate::LiveStreamingWithBackups), or
240/// [`FileBackup`](crate::FileBackup)). The available features — checkpointing, retry, disk
241/// backups — depend entirely on the transport mode chosen at build time.
242///
243/// Construct a `SiftStream` via [`SiftStreamBuilder`](builder::SiftStreamBuilder). Refer to the
244/// [crate-level documentation](crate) for mode comparison, examples, and tuning guidance.
245pub struct SiftStream<E, T> {
246    grpc_channel: SiftChannel,
247    encoder: E,
248    transport: T,
249    run: Option<Run>,
250    sift_stream_id: Uuid,
251}
252
253impl<E, T> SiftStream<E, T>
254where
255    E: Encoder + MetricsSnapshot,
256    T: Transport<Encoder = E>,
257{
258    #[cfg(feature = "metrics-unstable")]
259    /// Retrieve a snapshot of the current metrics for this stream.
260    pub fn get_metrics_snapshot(&self) -> SiftStreamMetricsSnapshot {
261        self.encoder.snapshot()
262    }
263
264    /// Attach a run to the stream. Any data provided through [SiftStream::send] after return
265    /// of this function will be associated with the run.
266    pub async fn attach_run(&mut self, run_selector: RunSelector) -> Result<()> {
267        let run = match run_selector {
268            RunSelector::ById(run_id) => load_run_by_id(self.grpc_channel.clone(), &run_id).await?,
269            RunSelector::ByForm(run_form) => {
270                load_run_by_form(self.grpc_channel.clone(), run_form).await?
271            }
272        };
273
274        self.run = Some(run);
275
276        Ok(())
277    }
278
279    /// Detach the run, if any, associated with the stream. Any data provided through [SiftStream::send] after
280    /// this function is called will not be associated with a run.
281    pub fn detach_run(&mut self) {
282        self.run = None;
283    }
284
285    /// Retrieves the attached run if it exists.
286    pub fn run(&self) -> Option<&Run> {
287        self.run.as_ref()
288    }
289
290    /// Send telemetry with backpressure.
291    ///
292    /// Encodes `message` and then awaits until the backing channel has capacity. See the
293    /// [`Transport`] implementation for specific details on backpressure.
294    ///
295    /// Use this method when you want the caller to slow down naturally when the pipeline
296    /// is under load. For a non-blocking alternative see [`try_send`](SiftStream::try_send).
297    ///
298    /// # Errors
299    ///
300    /// - [`SiftStreamSendError::EncodeError`] — the message could not be encoded. This
301    ///   indicates a schema mismatch or invalid value and is not recoverable by retrying.
302    /// - [`SiftStreamSendError::ChannelClosed`] — the backing channel was closed before the
303    ///   message could be delivered. The undelivered message is returned inside the variant.
304    ///
305    /// # Cancellation safety
306    ///
307    /// If the returned future is dropped while waiting for channel capacity, no message is
308    /// lost — either the send completed before the drop, or the channel slot was never taken.
309    pub async fn send<M>(
310        &mut self,
311        message: M,
312    ) -> std::result::Result<(), SiftStreamSendError<<T as Transport>::Message>>
313    where
314        M: Encodeable<Encoder = E, Output = <T as Transport>::Message> + Send + Sync,
315    {
316        let encoded = message
317            .encode(&mut self.encoder, &self.sift_stream_id, self.run.as_ref())
318            .ok_or_else(|| SiftStreamSendError::encode_error("Failed to encode message"))?;
319
320        self.transport
321            .send(&self.sift_stream_id, encoded)
322            .await
323            .map_err(|SendError(msg)| SiftStreamSendError::ChannelClosed(msg))
324    }
325
326    /// Send a batch of pre-encoded requests with backpressure.
327    ///
328    /// Awaits channel capacity for each request in turn. Stops on the first failure and
329    /// returns all undelivered messages (the failing one plus any not yet attempted).
330    ///
331    /// Unlike [`send`](SiftStream::send), this method accepts pre-encoded
332    /// [`Transport::Message`](crate::stream::Transport::Message) values directly, bypassing
333    /// the encode step. Use [`FlowBuilder`](crate::FlowBuilder) to construct them for maximum
334    /// performance.
335    ///
336    /// # Errors
337    ///
338    /// [`SendError<Vec<T>>`] containing every message that was not delivered.
339    pub async fn send_requests<I>(
340        &mut self,
341        requests: I,
342    ) -> std::result::Result<(), SendError<Vec<<T as Transport>::Message>>>
343    where
344        I: IntoIterator<Item = <T as Transport>::Message> + Send,
345        I::IntoIter: Send,
346    {
347        self.transport
348            .send_requests(&self.sift_stream_id, requests)
349            .await
350    }
351
352    /// Attempt to send telemetry without blocking.
353    ///
354    /// Encodes `message` and immediately attempts to place it on the backing channel. Returns
355    /// at once regardless of whether the channel has capacity.
356    ///
357    /// Use this method in tight loops or real-time contexts where blocking is unacceptable.
358    /// For backpressure-aware sending see [`send`](SiftStream::send).
359    ///
360    /// # Errors
361    ///
362    /// - [`SiftStreamTrySendError::EncodeError`] — the message could not be encoded.
363    /// - [`SiftStreamTrySendError::Channel`] wrapping one of:
364    ///   - [`TrySendError::Full`] — the backing channel is at capacity; the undelivered
365    ///     message is returned. Consider switching to [`send`](SiftStream::send) to apply
366    ///     backpressure, or retrying after a short delay.
367    ///   - [`TrySendError::Closed`] — the backing channel has been closed; the undelivered
368    ///     message is returned.
369    pub fn try_send<M>(
370        &mut self,
371        message: M,
372    ) -> std::result::Result<(), SiftStreamTrySendError<<T as Transport>::Message>>
373    where
374        M: Encodeable<Encoder = E, Output = <T as Transport>::Message> + Send + Sync,
375    {
376        let encoded = message
377            .encode(&mut self.encoder, &self.sift_stream_id, self.run.as_ref())
378            .ok_or_else(|| SiftStreamTrySendError::encode_error("Failed to encode message"))?;
379
380        self.transport
381            .try_send(&self.sift_stream_id, encoded)
382            .map_err(SiftStreamTrySendError::Channel)
383    }
384
385    /// Attempt to send a batch of pre-encoded requests without blocking.
386    ///
387    /// Calls `try_send` on the backing channel for each request. Returns immediately on
388    /// the first failure with every undelivered message (the failing one plus any not yet
389    /// attempted).
390    ///
391    /// Unlike [`try_send`](SiftStream::try_send), this method accepts pre-encoded
392    /// [`Transport::Message`](crate::stream::Transport::Message) values directly. Use
393    /// [`FlowBuilder`](crate::FlowBuilder) to construct them for maximum performance.
394    ///
395    /// # Errors
396    ///
397    /// [`TrySendError<Vec<T>>`] containing every message that was not delivered:
398    /// - [`TrySendError::Full`] — the backing channel was at capacity.
399    /// - [`TrySendError::Closed`] — the backing channel was closed.
400    pub fn try_send_requests<I>(
401        &mut self,
402        requests: I,
403    ) -> std::result::Result<(), TrySendError<Vec<<T as Transport>::Message>>>
404    where
405        I: IntoIterator<Item = <T as Transport>::Message> + Send,
406        I::IntoIter: Send,
407    {
408        self.transport
409            .try_send_requests(&self.sift_stream_id, requests)
410    }
411
412    /// Gracefully finish the stream, draining any remaining data before returning.
413    ///
414    /// It is important to always call this method when you are done sending data and
415    /// before the object is dropped.
416    pub async fn finish(self) -> Result<()> {
417        self.transport.finish(&self.sift_stream_id).await
418    }
419}
420
421impl<E, T> std::ops::Deref for SiftStream<E, T>
422where
423    E: Encoder + MetricsSnapshot,
424    T: Transport<Encoder = E>,
425{
426    type Target = E;
427    fn deref(&self) -> &Self::Target {
428        &self.encoder
429    }
430}
431
432impl<E, T> std::ops::DerefMut for SiftStream<E, T>
433where
434    E: Encoder + MetricsSnapshot,
435    T: Transport<Encoder = E>,
436{
437    fn deref_mut(&mut self) -> &mut Self::Target {
438        &mut self.encoder
439    }
440}
441
442/// Sealed trait to prevent external implementations of `SiftStreamMode`.
443mod private {
444    /// This trait is sealed and cannot be implemented outside this crate.
445    ///
446    /// It is public so it can be used as a supertrait, but the module is private,
447    /// preventing external code from implementing it.
448    pub trait Sealed {}
449}