1use std::borrow::Borrow;
18use std::ops::Deref;
19use std::sync::Arc;
20use crate::MemorySize;
23use crate::arrow::RetainedMemory;
24use crate::capacity::Reservation;
25
26pub type ReadPages<T> = ReadBatch<Vec<ReadBatch<Vec<T>>>>;
34
35pub struct ReadBatch<T> {
38 inner: Arc<ReadOwner<T>>,
39}
40
41struct ReadOwner<T> {
42 value: T,
43 _memory: Vec<RetainedMemory>,
44}
45
46impl<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 #[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 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 #[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 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#[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}