Skip to main content

harmos_storage/
read_batch.rs

1/* ***
2 *
3 * Harmos
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * ***
8 */
9
10/* === Modules === */
11
12/* === Re-Exports === */
13
14/* === Imports === */
15
16// Standard Library Imports
17use std::borrow::Borrow;
18use std::ops::Deref;
19use std::sync::Arc;
20// Third Party Imports
21// Local Imports
22use crate::MemorySize;
23use crate::arrow::RetainedMemory;
24use crate::capacity::Reservation;
25
26/* === Constants === */
27
28/* === Macros === */
29
30/* === Type Aliases === */
31
32/// A retained collection of original typed row pages.
33pub type ReadPages<T> = ReadBatch<Vec<ReadBatch<Vec<T>>>>;
34
35/* === Structs === */
36
37pub struct ReadBatch<T> {
38    inner: Arc<ReadOwner<T>>,
39}
40
41struct ReadOwner<T> {
42    value: T,
43    _memory: Vec<RetainedMemory>,
44}
45
46/* === Enums === */
47
48/* === Traits === */
49
50/* === Implementations === */
51
52impl<C> crate::StorageManager<C> {
53    #[tracing::instrument(name = "storage.read_batch.read_memory", level = "trace", skip_all, fields(bytes = bytes.get(), outcome = "cancelled"))]
54    pub async fn read_memory(&self, bytes: std::num::NonZeroUsize) -> crate::Result<RetainedMemory> {
55        let result = async {
56            crate::capacity::Reservation::wait_lifecycle(
57                &self.inner,
58                crate::capacity::Pool::Read,
59                &crate::capacity::Held::default(),
60                bytes.get(),
61            )
62            .await
63            .map(Reservation::retain)
64            .map_err(crate::capacity::Refusal::into_error)
65        }
66        .await;
67        tracing::Span::current().record("outcome", if result.is_ok() { "completed" } else { "failed" });
68        result
69    }
70}
71
72impl<T> ReadBatch<T> {
73    pub(crate) fn new(value: T, reservation: Reservation) -> Self {
74        Self::retained(value, reservation.retain())
75    }
76
77    pub(crate) fn retained(value: T, memory: RetainedMemory) -> Self {
78        Self { inner: Arc::new(ReadOwner { value, _memory: vec![memory] }) }
79    }
80
81    /// Consumes a unique owner and preserves its admitted memory through a projection.
82    ///
83    /// `additional_peak` must bound all new output allocations and conversion scratch
84    /// simultaneously live with the input. Private owner growth is admitted separately.
85    /// The callback must transfer payload allocations only into its successful value.
86    /// Errors and external captures must not retain them. Nested retained pages must
87    /// retain or map their own owners.
88    ///
89    /// Admission is nonblocking and happens before the callback. Shared owners are
90    /// refused; failure consumes this handle. Existing and additional claims remain
91    /// conservative until their last owner drops, including on independently shared
92    /// memory claims. No automatic shrinking or payload cloning occurs.
93    #[tracing::instrument(name = "storage.read_batch.try_map", level = "trace", skip_all, fields(additional_peak = additional_peak, outcome = "cancelled"))]
94    pub fn try_map<U, E>(
95        self, additional_peak: usize, map: impl FnOnce(T) -> std::result::Result<U, E>,
96    ) -> std::result::Result<ReadBatch<U>, E>
97    where
98        E: From<crate::Error>,
99    {
100        let result: std::result::Result<crate::ReadBatch<U>, E> = (|| {
101            let shared = || crate::Error::InvalidInput { field: "read projection", reason: "requires a unique owner" };
102            // Weak aliases can retain the old Arc allocation after its value moves.
103            let mut inner = self.inner;
104            if Arc::get_mut(&mut inner).is_none() {
105                return Err(shared().into());
106            }
107            let owner = Arc::try_unwrap(inner).map_err(|_| E::from(shared()))?;
108            let growth = std::mem::size_of::<ReadOwner<U>>().saturating_sub(std::mem::size_of::<ReadOwner<T>>());
109            let additional = additional_peak
110                .checked_add(growth)
111                .ok_or_else(|| E::from(crate::Error::ResourceExhausted { resource: "read projection memory" }))?;
112            if additional != 0 {
113                owner._memory.first().expect("read owner retains admission").try_grow_by(additional).map_err(E::from)?;
114            }
115            let ReadOwner { value, _memory } = owner;
116            let value = map(value)?;
117            Ok(ReadBatch { inner: Arc::new(ReadOwner { value, _memory }) })
118        })();
119        tracing::Span::current().record("outcome", if result.is_ok() { "completed" } else { "failed" });
120        result
121    }
122
123    /// Consumes two unique owners while retaining every input memory claim.
124    ///
125    /// The callback has the same peak-allocation and payload-escape obligations as
126    /// `try_map`. Guard-vector replacement and private owner growth are admitted
127    /// separately before allocating. Admission never waits and failure consumes
128    /// both handles. Shared or weak owner aliases are refused before the callback.
129    ///
130    /// Combining claims does not unify their admission groups. Inputs used by later
131    /// dependent reads should originate from the same `ReadSession`. Nested pages
132    /// must continue to retain or map their own guards.
133    #[tracing::instrument(name = "storage.read_batch.try_map_with", level = "trace", skip_all, fields(additional_peak = additional_peak, outcome = "cancelled"))]
134    pub fn try_map_with<U, V, E>(
135        self, other: ReadBatch<U>, additional_peak: usize, map: impl FnOnce(T, U) -> std::result::Result<V, E>,
136    ) -> std::result::Result<ReadBatch<V>, E>
137    where
138        E: From<crate::Error>,
139    {
140        let result: std::result::Result<crate::ReadBatch<V>, E> = (|| {
141            let shared = || crate::Error::InvalidInput { field: "read projection", reason: "requires unique owners" };
142            let exhausted = || crate::Error::ResourceExhausted { resource: "read projection memory" };
143            let mut left = self.inner;
144            let mut right = other.inner;
145            if Arc::get_mut(&mut left).is_none() || Arc::get_mut(&mut right).is_none() {
146                return Err(shared().into());
147            }
148            let mut left = Arc::try_unwrap(left).map_err(|_| E::from(shared()))?;
149            let mut right = Arc::try_unwrap(right).map_err(|_| E::from(shared()))?;
150            let count = left._memory.len().checked_add(right._memory.len()).ok_or_else(|| E::from(exhausted()))?;
151            // A Replacement Lives Alongside Both Original Guard Arrays.
152            let target = if count > left._memory.capacity() {
153                left._memory.capacity().checked_mul(2).unwrap_or(count).max(count)
154            } else {
155                left._memory.capacity()
156            };
157            let metadata = if target > left._memory.capacity() {
158                target.checked_mul(std::mem::size_of::<RetainedMemory>()).ok_or_else(|| E::from(exhausted()))?
159            } else {
160                0
161            };
162            let growth = std::mem::size_of::<ReadOwner<V>>().saturating_sub(std::mem::size_of::<ReadOwner<T>>());
163            let additional =
164                additional_peak.checked_add(metadata).and_then(|bytes| bytes.checked_add(growth)).ok_or_else(|| E::from(exhausted()))?;
165            if additional != 0 {
166                left._memory.first().expect("read owner retains admission").try_grow_by(additional).map_err(E::from)?;
167            }
168            if target > left._memory.capacity() {
169                left._memory.try_reserve_exact(target - left._memory.len()).map_err(|_| E::from(exhausted()))?;
170            }
171            left._memory.append(&mut right._memory);
172            let ReadOwner { value: left, _memory } = left;
173            let ReadOwner { value: right, _memory: empty } = right;
174            drop(empty);
175            let value = map(left, right)?;
176            Ok(ReadBatch { inner: Arc::new(ReadOwner { value, _memory }) })
177        })();
178        tracing::Span::current().record("outcome", if result.is_ok() { "completed" } else { "failed" });
179        result
180    }
181
182    pub(crate) fn owner_bytes() -> crate::Result<usize> {
183        std::mem::size_of::<ReadOwner<T>>()
184            .checked_add(2 * std::mem::size_of::<usize>())
185            .and_then(|bytes| bytes.checked_add(std::mem::size_of::<RetainedMemory>()))
186            .ok_or(crate::Error::ResourceExhausted { resource: "read projection memory" })
187    }
188
189    pub fn owner(&self) -> Arc<impl Borrow<T> + Send + Sync + 'static>
190    where
191        T: Send + Sync + 'static,
192    {
193        Arc::clone(&self.inner)
194    }
195}
196
197impl<T: std::fmt::Debug> std::fmt::Debug for ReadBatch<T> {
198    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199        formatter.debug_tuple("ReadBatch").field(&self.inner.value).finish()
200    }
201}
202
203impl<T> Clone for ReadBatch<T> {
204    fn clone(&self) -> Self {
205        Self { inner: Arc::clone(&self.inner) }
206    }
207}
208
209impl<T> Deref for ReadBatch<T> {
210    type Target = T;
211
212    fn deref(&self) -> &Self::Target {
213        &self.inner.value
214    }
215}
216
217impl<T> AsRef<T> for ReadBatch<T> {
218    fn as_ref(&self) -> &T {
219        &self.inner.value
220    }
221}
222
223impl<T> Borrow<T> for ReadOwner<T> {
224    fn borrow(&self) -> &T {
225        &self.value
226    }
227}
228
229impl<T: MemorySize> MemorySize for ReadBatch<T> {
230    fn memory_size(&self) -> usize {
231        std::mem::size_of::<Self>()
232            .saturating_add(std::mem::size_of::<ReadOwner<T>>())
233            .saturating_add(2 * std::mem::size_of::<usize>())
234            .saturating_add(self.inner._memory.capacity().saturating_mul(std::mem::size_of::<RetainedMemory>()))
235            .saturating_add(self.inner.value.memory_size().saturating_sub(std::mem::size_of::<T>()))
236    }
237}
238
239/* === Public Functions === */
240
241/* === Private Functions === */
242
243/* === Tests === */
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248    use crate::capacity::{Held, Pool};
249    use crate::test_backend::{Data, Recovery, history, storage_settings};
250
251    fn storage() -> crate::StorageManager {
252        let storage = crate::StorageManager::new(
253            crate::StorageBackends { primary: Arc::new(Data::<()>::new(history())), state: Arc::new(Recovery::new(history())) },
254            storage_settings(),
255        )
256        .unwrap();
257        {
258            let mut state = storage.inner.state.lock().unwrap();
259            state.ready = true;
260            state.status.running = true;
261        }
262        storage
263    }
264
265    #[test]
266    fn owner_retention() {
267        struct Payload(Vec<u8>);
268        let storage = storage();
269        let reservation = Reservation::try_new(&storage.inner, Pool::Read, &Held::default(), 4096).unwrap();
270        let values = vec![7; 2048];
271        let pointer = values.as_ptr();
272        let batch = ReadBatch::new(Payload(values), reservation);
273        let alias = batch.clone();
274        let owner = alias.owner();
275        assert_eq!(batch.0.as_ptr(), pointer);
276        drop(batch);
277        drop(alias);
278        assert_eq!(storage.status().read_bytes, 4096);
279        let value: &Payload = owner.as_ref().borrow();
280        assert_eq!(value.0.as_ptr(), pointer);
281        drop(owner);
282        assert_eq!(storage.status().read_bytes, 0);
283    }
284    #[test]
285    fn projection_moves() {
286        let storage = storage();
287        let bytes = storage.inner.settings.budget.read_memory_bytes.get();
288        let memory = Reservation::try_new(&storage.inner, Pool::Read, &Held::default(), bytes).unwrap();
289        let payload = vec![7u8; 32];
290        let pointer = payload.as_ptr();
291        let batch = ReadBatch::new((payload, 42u64), memory);
292        let mapped = batch.try_map(0, |(value, _)| Ok::<_, crate::Error>(value)).unwrap();
293        assert_eq!(mapped.as_ptr(), pointer);
294        assert_eq!(storage.status().read_bytes, bytes);
295        drop(mapped);
296        assert_eq!(storage.status().read_bytes, 0);
297    }
298
299    #[test]
300    fn projection_retains() {
301        let storage = storage();
302        let memory = Reservation::try_new(&storage.inner, Pool::Read, &Held::default(), 4096).unwrap().retain();
303        let alias = memory.clone();
304        let batch = ReadBatch::retained(vec![7u8; 32], memory);
305        let mapped = batch.try_map(256, Ok::<_, crate::Error>).unwrap();
306        assert_eq!(alias.bytes(), 4352);
307        let peer = mapped.clone();
308        drop(mapped);
309        assert_eq!(storage.status().read_bytes, 4352);
310        drop(peer);
311        assert_eq!(storage.status().read_bytes, 4352);
312        drop(alias);
313        assert_eq!(storage.status().read_bytes, 0);
314    }
315
316    #[test]
317    fn projection_refuses() {
318        let storage = storage();
319        let bytes = storage.inner.settings.budget.read_memory_bytes.get();
320        let memory = Reservation::try_new(&storage.inner, Pool::Read, &Held::default(), bytes).unwrap();
321        let batch = ReadBatch::new(vec![7u8; 32], memory);
322        let called = std::cell::Cell::new(false);
323        let result = batch.try_map(1, |value| {
324            called.set(true);
325            Ok::<_, crate::Error>(value)
326        });
327        assert!(result.is_err());
328        assert!(!called.get());
329        assert_eq!(storage.status().read_bytes, 0);
330    }
331
332    #[test]
333    fn shared_projection_refuses() {
334        let storage = storage();
335        let memory = Reservation::try_new(&storage.inner, Pool::Read, &Held::default(), 4096).unwrap();
336        let batch = ReadBatch::new(vec![7u8; 32], memory);
337        let peer = batch.clone();
338        let called = std::cell::Cell::new(false);
339        let result = batch.try_map(0, |value| {
340            called.set(true);
341            Ok::<_, crate::Error>(value)
342        });
343        assert!(result.is_err());
344        assert!(!called.get());
345        assert_eq!(peer.as_slice(), &[7u8; 32]);
346        assert_eq!(storage.status().read_bytes, 4096);
347        drop(peer);
348        assert_eq!(storage.status().read_bytes, 0);
349    }
350
351    #[test]
352    fn projection_failure_releases() {
353        #[derive(Debug)]
354        enum Failure {
355            Storage,
356            Conversion,
357        }
358        impl From<crate::Error> for Failure {
359            fn from(_: crate::Error) -> Self {
360                Self::Storage
361            }
362        }
363        let storage = storage();
364        let memory = Reservation::try_new(&storage.inner, Pool::Read, &Held::default(), 4096).unwrap();
365        let batch = ReadBatch::new(vec![7u8; 32], memory);
366        let result = batch.try_map(256, |_| Err::<Vec<u8>, _>(Failure::Conversion));
367        assert!(matches!(result, Err(Failure::Conversion)));
368        assert_eq!(storage.status().read_bytes, 0);
369    }
370
371    #[test]
372    fn projection_owner_growth() {
373        let storage = storage();
374        let memory = Reservation::try_new(&storage.inner, Pool::Read, &Held::default(), 4096).unwrap();
375        let batch = ReadBatch::new(7u8, memory);
376        let mapped = batch.try_map(0, |value| Ok::<_, crate::Error>([value; 128])).unwrap();
377        let growth = std::mem::size_of::<ReadOwner<[u8; 128]>>() - std::mem::size_of::<ReadOwner<u8>>();
378        assert_eq!(storage.status().read_bytes, 4096 + growth);
379        assert_eq!(mapped.as_slice(), &[7u8; 128]);
380        drop(mapped);
381        assert_eq!(storage.status().read_bytes, 0);
382    }
383
384    #[test]
385    fn incremental_growth() {
386        let storage = storage();
387        let memory = Reservation::try_new(&storage.inner, Pool::Read, &Held::default(), 4096).unwrap().retain();
388        std::thread::scope(|scope| {
389            for _ in 0..2 {
390                let memory = memory.clone();
391                scope.spawn(move || {
392                    for _ in 0..64 {
393                        memory.try_grow_by(1).unwrap();
394                    }
395                });
396            }
397        });
398        assert_eq!(memory.bytes(), 4224);
399        assert_eq!(storage.status().read_bytes, 4224);
400        drop(memory);
401        assert_eq!(storage.status().read_bytes, 0);
402    }
403    #[test]
404    fn weak_projection_refuses() {
405        let storage = storage();
406        let memory = Reservation::try_new(&storage.inner, Pool::Read, &Held::default(), 4096).unwrap();
407        let batch = ReadBatch::new(vec![7u8; 32], memory);
408        let weak = Arc::downgrade(&batch.inner);
409        let called = std::cell::Cell::new(false);
410        let result = batch.try_map(0, |value| {
411            called.set(true);
412            Ok::<_, crate::Error>(value)
413        });
414        assert!(result.is_err());
415        assert!(!called.get());
416        assert!(weak.upgrade().is_none());
417        assert_eq!(storage.status().read_bytes, 0);
418    }
419    fn admitted<T>(storage: &crate::StorageManager, value: T, bytes: usize) -> ReadBatch<T> {
420        ReadBatch::new(value, Reservation::try_new(&storage.inner, Pool::Read, &Held::default(), bytes).unwrap())
421    }
422
423    #[test]
424    fn merge_retains_inputs() {
425        let storage = storage();
426        let left = admitted(&storage, vec![7u8; 32], 4096);
427        let right = admitted(&storage, String::from("retained"), 4096);
428        let pointers = (left.as_ptr(), right.as_ptr());
429        let first = left.inner._memory[0].clone();
430        let second = right.inner._memory[0].clone();
431        let merged = left.try_map_with(right, 64, |left, right| Ok::<_, crate::Error>((left, right))).unwrap();
432        assert_eq!((merged.0.as_ptr(), merged.1.as_ptr()), pointers);
433        let held = first.bytes() + second.bytes();
434        assert!(held > 8192);
435        assert_eq!(storage.status().read_bytes, held);
436        let peer = merged.clone();
437        drop(merged);
438        drop(peer);
439        assert_eq!(storage.status().read_bytes, held);
440        drop(first);
441        assert_eq!(storage.status().read_bytes, second.bytes());
442        drop(second);
443        assert_eq!(storage.status().read_bytes, 0);
444    }
445
446    #[test]
447    fn merge_shared_refuses() {
448        for share_right in [false, true] {
449            let storage = storage();
450            let left = admitted(&storage, vec![7u8; 32], 4096);
451            let right = admitted(&storage, vec![8u8; 32], 4096);
452            let peer = if share_right { right.clone() } else { left.clone() };
453            let called = std::cell::Cell::new(false);
454            let result = left.try_map_with(right, 0, |left, right| {
455                called.set(true);
456                Ok::<_, crate::Error>((left, right))
457            });
458            assert!(result.is_err());
459            assert!(!called.get());
460            assert_eq!(peer.len(), 32);
461            assert_eq!(storage.status().read_bytes, 4096);
462            drop(peer);
463            assert_eq!(storage.status().read_bytes, 0);
464        }
465    }
466
467    #[test]
468    fn merge_metadata_refuses() {
469        let storage = storage();
470        let budget = storage.inner.settings.budget.read_memory_bytes.get();
471        let left = admitted(&storage, vec![7u8; 32], budget / 2);
472        let right = admitted(&storage, vec![8u8; 32], budget - budget / 2);
473        let called = std::cell::Cell::new(false);
474        let result = left.try_map_with(right, 0, |left, _| {
475            called.set(true);
476            Ok::<_, crate::Error>(left)
477        });
478        assert!(result.is_err());
479        assert!(!called.get());
480        assert_eq!(storage.status().read_bytes, 0);
481    }
482
483    #[test]
484    fn merge_overflow_refuses() {
485        let storage = storage();
486        let left = admitted(&storage, vec![7u8; 32], 4096);
487        let right = admitted(&storage, vec![8u8; 32], 4096);
488        let result = left.try_map_with(right, usize::MAX, |_, _| -> crate::Result<()> {
489            panic!("overflow must refuse before conversion");
490        });
491        assert!(matches!(result, Err(crate::Error::ResourceExhausted { .. })));
492        assert_eq!(storage.status().read_bytes, 0);
493    }
494
495    #[test]
496    fn merge_failure_releases() {
497        let storage = storage();
498        let left = admitted(&storage, vec![7u8; 32], 4096);
499        let right = admitted(&storage, String::from("retained"), 4096);
500        let result = left.try_map_with(right, 64, |_, _| -> crate::Result<()> {
501            Err(crate::Error::InvalidInput { field: "projection", reason: "injected conversion failure" })
502        });
503        assert!(matches!(result, Err(crate::Error::InvalidInput { .. })));
504        assert_eq!(storage.status().read_bytes, 0);
505    }
506
507    #[test]
508    fn merge_folds_pages() {
509        let storage = storage();
510        let first = vec![7u8; 32];
511        let mut pointers = vec![first.as_ptr()];
512        let mut merged = admitted(&storage, vec![first], 4096);
513        for byte in [8u8, 9] {
514            let value = vec![byte; 32];
515            pointers.push(value.as_ptr());
516            let page = admitted(&storage, value, 4096);
517            merged = merged
518                .try_map_with(page, 4 * std::mem::size_of::<Vec<u8>>(), |mut values, value| {
519                    values.push(value);
520                    Ok::<_, crate::Error>(values)
521                })
522                .unwrap();
523        }
524        assert_eq!(merged.iter().map(|value| value.as_ptr()).collect::<Vec<_>>(), pointers);
525        assert!(storage.status().read_bytes >= 3 * 4096);
526        let peer = merged.clone();
527        drop(merged);
528        assert_eq!(peer.len(), 3);
529        drop(peer);
530        assert_eq!(storage.status().read_bytes, 0);
531    }
532    #[test]
533    fn merge_weak_refuses() {
534        let storage = storage();
535        let left = admitted(&storage, vec![7u8; 32], 4096);
536        let right = admitted(&storage, vec![8u8; 32], 4096);
537        let weak = Arc::downgrade(&right.inner);
538        let called = std::cell::Cell::new(false);
539        let result = left.try_map_with(right, 0, |left, right| {
540            called.set(true);
541            Ok::<_, crate::Error>((left, right))
542        });
543        assert!(result.is_err());
544        assert!(!called.get());
545        assert!(weak.upgrade().is_none());
546        assert_eq!(storage.status().read_bytes, 0);
547    }
548    #[test]
549    fn merge_metadata_bounded() {
550        let storage = storage();
551        let count = 128usize;
552        let mut merged = admitted(&storage, vec![vec![7u8; 32]], 4096);
553        for _ in 1..count {
554            let page = admitted(&storage, vec![8u8; 32], 4096);
555            let peak = if merged.len() == merged.capacity() {
556                merged.capacity().checked_mul(2).unwrap().max(4) * std::mem::size_of::<Vec<u8>>()
557            } else {
558                0
559            };
560            merged = merged
561                .try_map_with(page, peak, |mut values, value| {
562                    values.push(value);
563                    Ok::<_, crate::Error>(values)
564                })
565                .unwrap();
566        }
567        assert_eq!(merged.len(), count);
568        let source = count * 4096;
569        let scaffold = count * 8 * (std::mem::size_of::<Vec<u8>>() + std::mem::size_of::<RetainedMemory>());
570        assert!(storage.status().read_bytes >= source);
571        assert!(storage.status().read_bytes - source <= scaffold);
572        drop(merged);
573        assert_eq!(storage.status().read_bytes, 0);
574    }
575}