laminar_core/streaming/
arrow_admission.rs1use std::sync::Arc;
4
5use arrow::array::{ArrayRef, RecordBatch};
6use tokio::sync::{OwnedSemaphorePermit, Semaphore, TryAcquireError};
7
8use super::StreamingError;
9
10pub const DEFAULT_SOURCE_MAX_QUEUED_BYTES: usize = 64 * 1024 * 1024;
12
13pub const MAX_SOURCE_QUEUED_BYTES: usize = if Semaphore::MAX_PERMITS < u32::MAX as usize {
15 Semaphore::MAX_PERMITS
16} else {
17 u32::MAX as usize
18};
19
20pub fn validate_source_max_queued_bytes(bytes: usize) -> Result<(), StreamingError> {
26 if bytes == 0 || bytes > MAX_SOURCE_QUEUED_BYTES {
27 return Err(StreamingError::InvalidConfig(
28 "max_queued_bytes must be nonzero and at most MAX_SOURCE_QUEUED_BYTES".into(),
29 ));
30 }
31 Ok(())
32}
33
34#[must_use]
40pub fn retained_arrow_bytes(batch: &RecordBatch) -> usize {
41 batch
42 .columns()
43 .iter()
44 .fold(std::mem::size_of::<RecordBatch>(), |total, column| {
45 total
46 .saturating_add(column.get_array_memory_size())
47 .saturating_add(std::mem::size_of::<ArrayRef>())
48 })
49}
50
51pub(crate) struct QueuedArrowBatch {
52 batch: RecordBatch,
53 _permit: OwnedSemaphorePermit,
54}
55
56impl QueuedArrowBatch {
57 pub(crate) fn admit(
58 batch: RecordBatch,
59 budget: &Arc<Semaphore>,
60 limit: usize,
61 ) -> Result<Self, StreamingError> {
62 validate_source_max_queued_bytes(limit)?;
63 let bytes = retained_arrow_bytes(&batch);
64 let permits = u32::try_from(bytes)
65 .ok()
66 .filter(|_| bytes <= limit)
67 .ok_or(StreamingError::BatchTooLarge { bytes, limit })?;
68 let permit =
69 Arc::clone(budget)
70 .try_acquire_many_owned(permits)
71 .map_err(|error| match error {
72 TryAcquireError::NoPermits => StreamingError::ChannelFull,
73 TryAcquireError::Closed => StreamingError::Disconnected,
74 })?;
75 Ok(Self {
76 batch,
77 _permit: permit,
78 })
79 }
80
81 pub(crate) fn into_batch(self) -> RecordBatch {
82 self.batch
83 }
84}