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}