molframe_core/execution/
batch.rs1use super::memory::BatchGuard;
4use super::{ExecutionContext, MemoryBudgetError, MemoryReservation};
5use std::sync::{Arc, Mutex};
6
7pub trait Batch {
9 fn rows(&self) -> usize;
11
12 fn retained_bytes(&self) -> usize;
14
15 fn is_empty(&self) -> bool {
17 self.rows() == 0
18 }
19}
20
21pub struct BatchLease<B: Batch> {
26 batch: LeaseBatch<B>,
27 batch_guard: Option<BatchGuard>,
28 reservation: Option<MemoryReservation>,
29 recycler: Option<Arc<BatchBufferPool<B>>>,
30}
31
32enum LeaseBatch<B> {
33 Owned(B),
34 Recyclable(Arc<B>),
35}
36
37impl<B: Batch> BatchLease<B> {
38 pub fn try_new(batch: B, context: &ExecutionContext) -> Result<Self, MemoryBudgetError> {
45 let reservation = context.try_reserve(batch.retained_bytes())?;
46 let batch_guard = reservation.begin_batch();
47 Ok(Self {
48 batch: LeaseBatch::Owned(batch),
49 batch_guard: Some(batch_guard),
50 reservation: Some(reservation),
51 recycler: None,
52 })
53 }
54
55 pub fn try_from_reservation(
62 batch: B,
63 mut reservation: MemoryReservation,
64 ) -> Result<Self, MemoryBudgetError> {
65 let retained = batch.retained_bytes();
66 if retained > reservation.bytes() {
67 drop(batch);
68 return Err(MemoryBudgetError::Exhausted {
69 requested: retained,
70 available: reservation.bytes(),
71 });
72 }
73 reservation.shrink_to(retained);
74 let batch_guard = reservation.begin_batch();
75 Ok(Self {
76 batch: LeaseBatch::Owned(batch),
77 batch_guard: Some(batch_guard),
78 reservation: Some(reservation),
79 recycler: None,
80 })
81 }
82
83 pub fn try_from_reservation_recycling(
92 batch: B,
93 mut reservation: MemoryReservation,
94 recycler: Arc<BatchBufferPool<B>>,
95 ) -> Result<Self, MemoryBudgetError> {
96 let retained = batch.retained_bytes();
97 if retained > reservation.bytes() {
98 drop(batch);
99 return Err(MemoryBudgetError::Exhausted {
100 requested: retained,
101 available: reservation.bytes(),
102 });
103 }
104 reservation.shrink_to(retained);
105 let batch_guard = reservation.begin_batch();
106 Ok(Self {
107 batch: LeaseBatch::Recyclable(Arc::new(batch)),
108 batch_guard: Some(batch_guard),
109 reservation: Some(reservation),
110 recycler: Some(recycler),
111 })
112 }
113
114 #[must_use]
116 pub fn batch(&self) -> &B {
117 match &self.batch {
118 LeaseBatch::Owned(batch) => batch,
119 LeaseBatch::Recyclable(batch) => batch,
120 }
121 }
122}
123
124impl<B: Batch> Drop for BatchLease<B> {
125 fn drop(&mut self) {
126 drop(self.batch_guard.take());
127 let Some(recycler) = &self.recycler else {
128 return;
129 };
130 let Some(reservation) = self.reservation.take() else {
131 return;
132 };
133 let LeaseBatch::Recyclable(batch) = &self.batch else {
134 return;
135 };
136 self.reservation = recycler.put_or_return(Arc::clone(batch), reservation);
137 }
138}
139
140impl<B: Batch + std::fmt::Debug> std::fmt::Debug for BatchLease<B> {
141 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142 formatter
143 .debug_struct("BatchLease")
144 .field("batch", self.batch())
145 .field("recycling", &self.recycler.is_some())
146 .finish_non_exhaustive()
147 }
148}
149
150#[derive(Debug)]
156pub struct BatchBufferPool<B: Batch> {
157 slot: Mutex<Option<(Arc<B>, MemoryReservation)>>,
158}
159
160impl<B: Batch> BatchBufferPool<B> {
161 #[must_use]
163 pub const fn new() -> Self {
164 Self {
165 slot: Mutex::new(None),
166 }
167 }
168
169 pub fn take(&self) -> Option<(B, MemoryReservation)> {
174 let Ok(mut slot) = self.slot.lock() else {
175 return None;
176 };
177 if slot
178 .as_ref()
179 .is_some_and(|(batch, _)| Arc::strong_count(batch) != 1)
180 {
181 return None;
182 }
183 let (batch, reservation) = slot.take()?;
184 match Arc::try_unwrap(batch) {
185 Ok(batch) => Some((batch, reservation)),
186 Err(batch) => {
187 *slot = Some((batch, reservation));
188 None
189 }
190 }
191 }
192
193 pub fn put(&self, batch: B, reservation: MemoryReservation) -> Result<(), MemoryBudgetError> {
200 let retained = batch.retained_bytes();
201 if retained > reservation.bytes() {
202 drop(batch);
203 return Err(MemoryBudgetError::Exhausted {
204 requested: retained,
205 available: reservation.bytes(),
206 });
207 }
208 drop(self.put_or_return(Arc::new(batch), reservation));
209 Ok(())
210 }
211
212 fn put_or_return(
215 &self,
216 batch: Arc<B>,
217 reservation: MemoryReservation,
218 ) -> Option<MemoryReservation> {
219 if let Ok(mut slot) = self.slot.lock()
220 && slot.is_none()
221 {
222 *slot = Some((batch, reservation));
223 return None;
224 }
225 drop(batch);
226 Some(reservation)
227 }
228}
229
230impl<B: Batch> Default for BatchBufferPool<B> {
231 fn default() -> Self {
232 Self::new()
233 }
234}
235
236#[derive(Clone, Copy, Debug, PartialEq, Eq)]
238pub struct BatchDemand {
239 pub max_rows: usize,
241 pub max_bytes: usize,
243}
244
245impl BatchDemand {
246 #[must_use]
249 pub const fn new(max_rows: usize, max_bytes: usize) -> Self {
250 Self {
251 max_rows,
252 max_bytes,
253 }
254 }
255
256 #[must_use]
258 pub const fn can_accept_work(self) -> bool {
259 self.max_rows > 0 && self.max_bytes > 0
260 }
261
262 #[must_use]
264 pub fn accepts<B: Batch>(self, batch: &B) -> bool {
265 batch.rows() <= self.max_rows && batch.retained_bytes() <= self.max_bytes
266 }
267}
268
269#[derive(Debug)]
271pub enum Backpressure<B> {
272 Ready(B),
274 Pending,
276 Finished,
278}
279
280pub trait BatchSource {
286 type Batch: Batch;
288 type Error;
290
291 fn next_batch(
298 &mut self,
299 demand: BatchDemand,
300 context: &ExecutionContext,
301 ) -> Result<Backpressure<BatchLease<Self::Batch>>, Self::Error>;
302}
303
304#[cfg(test)]
305#[path = "batch_tests.rs"]
306mod tests;