laminar_core/streaming/source/
mod.rs1use std::sync::atomic::{AtomicI64, AtomicU64, Ordering};
4use std::sync::{Arc, OnceLock};
5use std::time::Duration;
6
7use arrow::array::RecordBatch;
8use arrow::datatypes::SchemaRef;
9use tokio::sync::Semaphore;
10
11use super::arrow_admission::QueuedArrowBatch;
12use super::channel::{channel_with_config, AsyncConsumer, Producer};
13use super::config::SourceConfig;
14use super::error::{StreamingError, TryPushError};
15use super::sink::Sink;
16
17pub trait Record: Clone + Send + Sized + 'static {
19 fn schema() -> SchemaRef;
21
22 fn to_record_batch(&self) -> RecordBatch;
26
27 fn event_time(&self) -> Option<i64> {
32 None
33 }
34
35 fn to_record_batch_from_iter<I>(records: I) -> RecordBatch
40 where
41 I: IntoIterator<Item = Self>,
42 {
43 let batches: Vec<RecordBatch> = records.into_iter().map(|r| r.to_record_batch()).collect();
44 if batches.is_empty() {
45 return RecordBatch::new_empty(Self::schema());
46 }
47 arrow::compute::concat_batches(&Self::schema(), &batches)
48 .unwrap_or_else(|_| RecordBatch::new_empty(Self::schema()))
49 }
50}
51
52pub(crate) enum SourceMessage<T> {
54 Record(T),
56
57 Batch(QueuedArrowBatch),
59}
60
61impl<T: Record> SourceMessage<T> {
62 pub(crate) fn into_batch(self) -> RecordBatch {
63 match self {
64 Self::Record(record) => record.to_record_batch(),
65 Self::Batch(batch) => batch.into_batch(),
66 }
67 }
68}
69
70struct SourceWatermark {
72 current: Arc<AtomicI64>,
76}
77
78impl SourceWatermark {
79 fn new() -> Self {
80 Self {
81 current: Arc::new(AtomicI64::new(i64::MIN)),
82 }
83 }
84
85 fn from_arc(arc: Arc<AtomicI64>) -> Self {
86 Self { current: arc }
87 }
88
89 fn update(&self, timestamp: i64) {
90 let mut current = self.current.load(Ordering::Acquire);
92 while timestamp > current {
93 match self.current.compare_exchange_weak(
94 current,
95 timestamp,
96 Ordering::AcqRel,
97 Ordering::Acquire,
98 ) {
99 Ok(_) => break,
100 Err(actual) => current = actual,
101 }
102 }
103 }
104
105 fn restore_for_recovery(&self, timestamp: i64) {
106 self.current.store(timestamp, Ordering::Release);
107 }
108
109 fn get(&self) -> i64 {
110 self.current.load(Ordering::Acquire)
111 }
112
113 fn arc(&self) -> Arc<AtomicI64> {
114 Arc::clone(&self.current)
115 }
116}
117
118struct SourceInner<T: Record> {
120 producer: Producer<SourceMessage<T>>,
122
123 arrow_budget: Arc<Semaphore>,
124 max_queued_bytes: usize,
125
126 watermark: SourceWatermark,
128
129 schema: SchemaRef,
131
132 name: Option<String>,
134
135 sequence: Arc<AtomicU64>,
138
139 event_time_column: OnceLock<String>,
142
143 max_out_of_orderness: OnceLock<Duration>,
146}
147
148pub struct Source<T: Record> {
153 inner: Arc<SourceInner<T>>,
154}
155
156impl<T: Record> Source<T> {
157 pub(crate) fn new(config: SourceConfig) -> (Self, Sink<T>) {
159 let (source, consumer) = Self::with_consumer(config);
160 let sink = Sink::new(consumer, source.schema());
161 (source, sink)
162 }
163
164 fn with_consumer(config: SourceConfig) -> (Self, AsyncConsumer<SourceMessage<T>>) {
165 let channel_config = config.channel;
166 let (producer, consumer) = channel_with_config::<SourceMessage<T>>(&channel_config);
167
168 let schema = T::schema();
169
170 let inner = Arc::new(SourceInner {
171 producer,
172 arrow_budget: Arc::new(Semaphore::new(
175 config.max_queued_bytes.min(super::MAX_SOURCE_QUEUED_BYTES),
176 )),
177 max_queued_bytes: config.max_queued_bytes,
178 watermark: SourceWatermark::new(),
179 schema,
180 name: config.name,
181 sequence: Arc::new(AtomicU64::new(0)),
182 event_time_column: OnceLock::new(),
183 max_out_of_orderness: OnceLock::new(),
184 });
185
186 (Self { inner }, consumer)
187 }
188
189 pub fn push(&self, record: T) -> Result<(), StreamingError> {
196 self.try_push(record).map_err(|error| error.error)
197 }
198
199 pub fn try_push(&self, record: T) -> Result<(), TryPushError<T>> {
205 let event_time = record.event_time();
206
207 self.inner
208 .producer
209 .try_push(SourceMessage::Record(record))
210 .map_err(|error| match error.value {
211 SourceMessage::Record(r) => TryPushError {
212 value: r,
213 error: error.error,
214 },
215 SourceMessage::Batch(_) => unreachable!("only Record is pushed here"),
216 })?;
217
218 if let Some(event_time) = event_time {
219 self.inner.watermark.update(event_time);
220 }
221 self.inner.sequence.fetch_add(1, Ordering::Relaxed);
222 Ok(())
223 }
224
225 pub fn push_batch(&self, records: &[T]) -> usize
227 where
228 T: Clone,
229 {
230 self.push_batch_drain(records.iter().cloned())
231 }
232
233 pub fn push_batch_drain<I>(&self, records: I) -> usize
236 where
237 I: IntoIterator<Item = T>,
238 {
239 let mut count = 0;
240 for record in records {
241 if self.push(record).is_err() {
242 break;
243 }
244 count += 1;
245 }
246 count
247 }
248
249 pub fn push_arrow(&self, batch: RecordBatch) -> Result<(), StreamingError> {
264 if !self.inner.schema.fields().is_empty() && batch.schema() != self.inner.schema {
266 return Err(StreamingError::SchemaMismatch {
267 expected: self
268 .inner
269 .schema
270 .fields()
271 .iter()
272 .map(|f| f.name().clone())
273 .collect(),
274 actual: batch
275 .schema()
276 .fields()
277 .iter()
278 .map(|f| f.name().clone())
279 .collect(),
280 });
281 }
282
283 if self.is_closed() {
284 return Err(StreamingError::Disconnected);
285 }
286 let queued =
287 QueuedArrowBatch::admit(batch, &self.inner.arrow_budget, self.inner.max_queued_bytes)?;
288 self.inner
289 .producer
290 .try_push(SourceMessage::Batch(queued))
291 .map_err(|error| error.error)?;
292
293 self.inner.sequence.fetch_add(1, Ordering::Relaxed);
294 Ok(())
295 }
296
297 pub fn watermark(&self, timestamp: i64) {
306 self.inner.watermark.update(timestamp);
311 }
312
313 pub fn restore_watermark_for_recovery(&self, timestamp: i64) {
320 self.inner.watermark.restore_for_recovery(timestamp);
321 }
322
323 #[must_use]
325 pub fn current_watermark(&self) -> i64 {
326 self.inner.watermark.get()
327 }
328
329 #[must_use]
331 pub fn schema(&self) -> SchemaRef {
332 Arc::clone(&self.inner.schema)
333 }
334
335 #[must_use]
337 pub fn name(&self) -> Option<&str> {
338 self.inner.name.as_deref()
339 }
340
341 #[must_use]
343 pub fn is_closed(&self) -> bool {
344 self.inner.producer.is_closed()
345 }
346
347 #[must_use]
349 pub fn pending(&self) -> usize {
350 self.inner.producer.len()
351 }
352
353 #[must_use]
355 pub fn capacity(&self) -> usize {
356 self.inner.producer.capacity()
357 }
358
359 #[must_use]
362 pub fn queued_arrow_bytes(&self) -> usize {
363 self.inner
364 .max_queued_bytes
365 .min(super::MAX_SOURCE_QUEUED_BYTES)
366 - self.inner.arrow_budget.available_permits()
367 }
368
369 #[must_use]
371 pub fn max_queued_bytes(&self) -> usize {
372 self.inner.max_queued_bytes
373 }
374
375 #[must_use]
377 pub fn sequence(&self) -> u64 {
378 self.inner.sequence.load(Ordering::Acquire)
379 }
380
381 #[must_use]
383 pub fn sequence_counter(&self) -> Arc<AtomicU64> {
384 Arc::clone(&self.inner.sequence)
385 }
386
387 #[must_use]
389 pub fn watermark_atomic(&self) -> Arc<AtomicI64> {
390 self.inner.watermark.arc()
391 }
392
393 pub fn set_event_time_column(&self, column: &str) {
400 let _ = self.inner.event_time_column.set(column.to_owned());
401 }
402
403 #[must_use]
405 pub fn event_time_column(&self) -> Option<String> {
406 self.inner.event_time_column.get().cloned()
407 }
408
409 pub fn set_max_out_of_orderness(&self, dur: Duration) {
413 let _ = self.inner.max_out_of_orderness.set(dur);
414 }
415
416 #[must_use]
418 pub fn max_out_of_orderness(&self) -> Option<Duration> {
419 self.inner.max_out_of_orderness.get().copied()
420 }
421}
422
423impl<T: Record> Clone for Source<T> {
424 fn clone(&self) -> Self {
425 let producer = self.inner.producer.clone();
426 let event_time_col = self.inner.event_time_column.get().cloned();
427 let event_time_column = OnceLock::new();
428 if let Some(col) = event_time_col {
429 let _ = event_time_column.set(col);
430 }
431 let max_ooo = self.inner.max_out_of_orderness.get().copied();
432 let max_out_of_orderness = OnceLock::new();
433 if let Some(dur) = max_ooo {
434 let _ = max_out_of_orderness.set(dur);
435 }
436 Self {
437 inner: Arc::new(SourceInner {
438 producer,
439 arrow_budget: Arc::clone(&self.inner.arrow_budget),
440 max_queued_bytes: self.inner.max_queued_bytes,
441 watermark: SourceWatermark::from_arc(self.inner.watermark.arc()),
442 schema: Arc::clone(&self.inner.schema),
443 name: self.inner.name.clone(),
444 sequence: Arc::clone(&self.inner.sequence),
445 event_time_column,
446 max_out_of_orderness,
447 }),
448 }
449 }
450}
451
452impl<T: Record + std::fmt::Debug> std::fmt::Debug for Source<T> {
453 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
454 f.debug_struct("Source")
455 .field("name", &self.inner.name)
456 .field("pending", &self.pending())
457 .field("capacity", &self.capacity())
458 .field("watermark", &self.current_watermark())
459 .finish()
460 }
461}
462
463#[must_use]
465pub fn create<T: Record>(buffer_size: usize) -> (Source<T>, Sink<T>) {
466 Source::new(SourceConfig::with_buffer_size(buffer_size))
467}
468
469#[must_use]
471pub fn create_with_config<T: Record>(config: SourceConfig) -> (Source<T>, Sink<T>) {
472 Source::new(config)
473}
474
475#[cfg(feature = "cluster")]
480#[must_use]
481pub fn create_for_schema_planning<T: Record>(config: SourceConfig) -> Source<T> {
482 Source::with_consumer(config).0
483}
484
485#[cfg(test)]
486mod tests;
487
488#[cfg(test)]
489mod admission_tests;