rudb_vector/buffer.rs
1//! The run of values behind a flat vector, and the seam the buffer manager arrives through.
2//!
3//! `spec/engine/03-data-plane.md` section 3.8. Today every vector owns its payload and every scan
4//! allocates, which is the right place to start and is not where this ends. At layer three the scan
5//! reads a page out of the buffer manager and the vector wants to point into that page rather than
6//! copy out of it, and the copy it avoids is the largest single copy in the system, because it is
7//! every byte of every column every query reads.
8//!
9//! The type that supports both is one enum holding either an owned run or a borrowed one, and the
10//! part that has to be decided correctly the first time is how the borrow is expressed, because that
11//! shows up in every signature that mentions a vector.
12//!
13//! # Why the pin is a handle and not a lifetime
14//!
15//! The obvious way to express a borrow in Rust is a lifetime parameter, and it is the wrong one
16//! here. A lifetime on [`Buffer`] is a lifetime on [`Data`](crate::Data), which is a lifetime on
17//! [`Vector`](crate::Vector), which is a lifetime on [`Chunk`](crate::Chunk), which is a lifetime on
18//! every operator's state, on every trait object in the pipeline, and on every queue a chunk is put
19//! into for another thread to pick up. The scheduler is exactly that last thing, so the borrow would
20//! have to outlive a hand off between threads that the compiler has no way to see the end of. The
21//! two ways out of that are unsafe code and a copy at the boundary, and the copy at the boundary is
22//! the thing the borrow existed to avoid.
23//!
24//! So the pin is a [`Pin`], a reference counted handle the buffer holds, and the page stays alive
25//! because the handle is alive rather than because a region ends. It costs one atomic increment per
26//! vector construction, which is not measurable next to reading the page it is protecting, and the
27//! ownership story stays uniform: a chunk is `Send`, always, whatever its columns are pointing at.
28//!
29//! # Why it landed with one variant
30//!
31//! There was no buffer manager, so there was nothing to borrow from, and writing the borrowed
32//! variant then would have been writing an interface against an imaginary caller. What landed was
33//! the enum, with only the owned variant in it, so that adding a variant later is a change inside
34//! this crate rather than a change to every signature in the workspace. The reader side of that
35//! migration was already done by the [`Deref`] below: everything outside this crate reads a slice,
36//! and a slice is what every variant hands back. The section after this one is that bet being
37//! collected, and it cost two functions.
38//!
39//! The writer side is [`Buffer::to_mut`], which is the one function that has to grow a case. A write
40//! through a borrowed buffer has to copy the page into an owned run first, which is what `Cow` does
41//! and for the same reason, and having the call site named now means that day is a change to one
42//! function rather than a search for every `push`.
43//!
44//! # The second variant arrived early, and from the other direction
45//!
46//! The shared variant is here before the buffer manager is, because the Parquet reader needed the
47//! same thing for a different reason. A string column is built over the page it was decoded from
48//! rather than copying out of it, so the page becomes the column's arena and goes downstream with
49//! it, and the reader never gets the allocation back. On `hits` that is ten and a half megabytes a
50//! row group, freed and taken again for every row group, and glibc hands a block that size back to
51//! the kernel when it is freed, so the next one faults in every page of it. Measured on the URL
52//! column of `hits-1m-snappy.parquet`, running with `MALLOC_MMAP_THRESHOLD_` and
53//! `MALLOC_TRIM_THRESHOLD_` both raised so that nothing is ever handed back took the query from
54//! 113.08 milliseconds to 104.08, and the decode stage moved as well as the decompress one, which
55//! is what says it is page faults rather than anything about the codec.
56//!
57//! So the arena is held by an [`Arc`] and the reader keeps a handle to it. When every column built
58//! over that page has been dropped the reader is the only holder left, takes the run back out and
59//! decompresses the next page into it. That is a page pool with two entries and no eviction policy,
60//! which is not the buffer manager, but it is the same shape and it is the first caller that will
61//! want one.
62//!
63//! It is [`Arc<Vec<T>>`] rather than [`Pin`] because this caller knows exactly what its page is and
64//! can say so in the type. The [`Pin`] variant is still coming and is still opaque, because the
65//! buffer manager's page is a frame in a pool that a vector has no business knowing the shape of.
66//! Two variants for two situations is the honest answer here: one of them can name its page and the
67//! other cannot.
68//!
69//! # A shared buffer is a window, not a whole page
70//!
71//! The shared variant carries an offset and a length, so a buffer can be a run inside a page rather
72//! than the whole of one. That is what makes [`Buffer::slice`] pointer arithmetic and a reference
73//! count bump instead of an allocation and a copy.
74//!
75//! It was a whole page for one release and the cost of that shows up everywhere a column is cut.
76//! Every other form of a vector could already be cut for nothing, because a bit packed body moves an
77//! offset, a dictionary body shares its values, a string body shares its arena and a run length body
78//! keeps the runs it touches. The flat body was the exception, and it was the exception because this
79//! type could not name a piece of itself, so `spec/perf/12-the-chunk-and-the-page.md` measured 1,803
80//! instructions a chunk of memcpy and 1,869 of malloc and free on a `sum` over an in memory table,
81//! which together were 27 percent of the chunk. None of that was the query's work.
82//!
83//! The owned variant does not grow a window. A `Vec<T>` is the thing a writer appends to and an
84//! offset on it would mean every `push` had to think about where the run starts, for no gain: a
85//! caller that means its run to be cut many times says so once, with [`Buffer::into_page`], and
86//! after that the cuts are free. Cutting an owned run still copies, which is the same answer `Cow`
87//! gives and is why [`Buffer::slice`] is where the decision is made rather than at each call site.
88
89use std::any::Any;
90use std::ops::Deref;
91use std::sync::Arc;
92
93/// What keeps a page alive for as long as a buffer points into it.
94///
95/// Opaque on purpose. The vector does not know what a page is and has no business looking inside
96/// one, it only has to hold the thing that stops the page being evicted, and the buffer manager at
97/// layer three decides what that thing is. The bounds are the load bearing part and they are here
98/// now: `Send` and `Sync`, because a chunk carrying one of these crosses a thread boundary every
99/// time the scheduler moves a pipeline, and `'static`, because a handle with a lifetime on it would
100/// have put the lifetime back on [`Vector`](crate::Vector) by another route.
101pub type Pin = Arc<dyn Any + Send + Sync>;
102
103/// A run of values of one physical type.
104///
105/// Derefs to a slice, which is how every reader in the workspace gets at it, so a reader does not
106/// know or care which variant it is holding. Writers go through [`Self::push`] and
107/// [`Self::to_mut`].
108#[derive(Debug, Clone)]
109pub struct Buffer<T> {
110 store: Store<T>,
111}
112
113/// Where the values actually are.
114///
115/// The third is a run inside a page, carrying the [`Pin`] that keeps the page where it is, and it
116/// arrives with the buffer manager because that is the only thing that can hand one out. It is an
117/// enum rather than a newtype around `Vec<T>` because the shape is what the rest of the workspace
118/// is compiled against, and a newtype that turns into an enum later is the flag day this exists to
119/// avoid.
120#[derive(Debug, Clone)]
121enum Store<T> {
122 /// The vector owns the values.
123 Owned(Vec<T>),
124 /// The values are a run inside a page somebody else is holding a handle to as well.
125 ///
126 /// `from` and `len` name the run, and the invariant is that `from + len <= page.len()`, which
127 /// every constructor below clamps to rather than asserting, because the callers that cut a
128 /// buffer are cutting it to a row count that a short body is allowed to fall short of.
129 ///
130 /// Read only, which is not enforced and does not need to be: the only way to write is
131 /// [`Buffer::to_mut`] and that copies out first, so a shared page is never written through even
132 /// by a caller that has forgotten what it is holding.
133 Shared { page: Arc<Vec<T>>, from: usize, len: usize },
134}
135
136impl<T> Buffer<T> {
137 /// An empty buffer.
138 #[must_use]
139 pub fn new() -> Self {
140 Self { store: Store::Owned(Vec::new()) }
141 }
142
143 /// An empty buffer with room for `capacity` values.
144 #[must_use]
145 pub fn with_capacity(capacity: usize) -> Self {
146 Self { store: Store::Owned(Vec::with_capacity(capacity)) }
147 }
148
149 /// A buffer owning `values`.
150 #[must_use]
151 pub fn from_vec(values: Vec<T>) -> Self {
152 Self { store: Store::Owned(values) }
153 }
154
155 /// A buffer over a whole page that somebody else is holding a handle to as well.
156 ///
157 /// The caller that wants this is one that decoded the page and is going to want the allocation
158 /// back when the last reader of it is gone, which it gets by keeping its own handle and waiting
159 /// for [`Arc::get_mut`] to start answering. Nothing here enforces that, and a caller that drops
160 /// its handle has simply built an owned buffer with an extra indirection.
161 #[must_use]
162 pub fn from_arc(page: Arc<Vec<T>>) -> Self {
163 let len = page.len();
164 Self { store: Store::Shared { page, from: 0, len } }
165 }
166
167 /// A buffer over a run inside a page that somebody else is holding a handle to as well.
168 ///
169 /// Clamped to the page, so a run that starts or ends past it comes back shorter rather than
170 /// panicking. The caller asking for this is a scan that knows where in the page its chunk
171 /// starts, and it is the constructor [`Self::slice`] reaches for once the buffer is shared.
172 #[must_use]
173 pub fn window(page: Arc<Vec<T>>, from: usize, len: usize) -> Self {
174 let from = from.min(page.len());
175 let len = len.min(page.len() - from);
176 Self { store: Store::Shared { page, from, len } }
177 }
178
179 /// Whether this buffer is a run inside a page it shares rather than one it owns.
180 ///
181 /// For a caller deciding whether a write is about to cost a copy of the page, and for the tests
182 /// that assert the reader did not quietly stop sharing.
183 #[must_use]
184 pub fn is_shared(&self) -> bool {
185 matches!(self.store, Store::Shared { .. })
186 }
187
188 /// This run as a page, so that every cut of it after this one is free.
189 ///
190 /// For a producer that has built a run it means to hand out in pieces. It costs one allocation
191 /// for the [`Arc`] and moves the run into it without copying the values, and after it the buffer
192 /// is read only in the sense that a write copies out. A buffer that is already a window comes
193 /// back unchanged, including its offset, so calling this twice is not two pages.
194 #[must_use]
195 pub fn into_page(self) -> Self {
196 match self.store {
197 Store::Owned(values) => Self::from_arc(Arc::new(values)),
198 shared @ Store::Shared { .. } => Self { store: shared },
199 }
200 }
201
202 /// How many bytes of memory this buffer is holding.
203 ///
204 /// Capacity rather than length, because capacity is what was taken from the allocator.
205 ///
206 /// A shared page is charged to the buffers over it in equal parts, which is an approximation
207 /// and is worth being plain about. The exact answer needs to know who else is holding the page
208 /// and what they are charging, and no buffer can see that. Splitting it means the live buffers
209 /// over one page add up to slightly less than the page, never to several times it, and that is
210 /// the direction to be wrong in: a scan that emits fifty chunks over one ten megabyte page
211 /// would otherwise report half a gigabyte and trip a memory limit that nothing came close to.
212 ///
213 /// It is the page and not the window, because the page is what is resident. A window over a
214 /// tenth of a page that nobody else is reading holds the whole page down, and reporting the
215 /// window would say a scan of one chunk out of a hundred megabyte page costs a megabyte, which
216 /// is the direction that lets a memory limit be passed by something that has not freed anything.
217 #[must_use]
218 pub fn footprint(&self) -> usize {
219 match &self.store {
220 Store::Owned(values) => values.capacity() * size_of::<T>(),
221 Store::Shared { page, .. } => {
222 page.capacity() * size_of::<T>() / Arc::strong_count(page)
223 }
224 }
225 }
226
227 /// The values.
228 #[must_use]
229 #[inline]
230 pub fn as_slice(&self) -> &[T] {
231 match &self.store {
232 Store::Owned(values) => values,
233 // The invariant on the variant is that this range is inside the page, and every
234 // constructor clamps to it, so the index cannot be out of bounds.
235 Store::Shared { page, from, len } => &page[*from..*from + *len],
236 }
237 }
238}
239
240impl<T: Clone> Buffer<T> {
241 /// The values as an owned run, copying only if they were not owned already.
242 ///
243 /// One of the two places that can copy a page, and it is spelled as a method rather than a
244 /// field access so that it is greppable. The copy is skipped when the caller turns out to hold
245 /// the last handle to the whole page, which is the common case for a buffer that was shared only
246 /// so that its producer could get the allocation back. A window over part of a page always
247 /// copies, because the run it names is not a `Vec` and cannot become one for free.
248 #[must_use]
249 pub fn into_vec(self) -> Vec<T> {
250 match self.store {
251 Store::Owned(values) => values,
252 Store::Shared { page, from, len } if from == 0 && len == page.len() => {
253 Arc::try_unwrap(page).unwrap_or_else(|page| page.to_vec())
254 }
255 Store::Shared { page, from, len } => page[from..from + len].to_vec(),
256 }
257 }
258
259 /// A run of `len` values starting at `from`, clamped to what is there.
260 ///
261 /// Free on a shared buffer, which is the whole point: the page is the same page, the offsets add
262 /// up, and the cost is one atomic increment. A copy on an owned one, because an owned run is a
263 /// `Vec` and a piece of a `Vec` is not one. A producer that means its run to be cut many times
264 /// calls [`Self::into_page`] first and then pays nothing per cut.
265 #[must_use]
266 pub fn slice(&self, from: usize, len: usize) -> Self {
267 match &self.store {
268 Store::Owned(values) => {
269 let from = from.min(values.len());
270 let len = len.min(values.len() - from);
271 Self::from_vec(values[from..from + len].to_vec())
272 }
273 Store::Shared { page, from: start, len: held } => {
274 let from = from.min(*held);
275 let len = len.min(*held - from);
276 Self { store: Store::Shared { page: Arc::clone(page), from: start + from, len } }
277 }
278 }
279 }
280
281 /// The values, writable, copying them out of the page first if they are not owned.
282 ///
283 /// The copy on write point, and the only one. Everything that mutates a buffer goes through
284 /// here, so a variant that is not owned needs a case in this function and in nothing else.
285 #[inline]
286 pub fn to_mut(&mut self) -> &mut Vec<T> {
287 if let Store::Shared { page, from, len } = &self.store {
288 self.store = Store::Owned(page[*from..*from + *len].to_vec());
289 }
290 match &mut self.store {
291 Store::Owned(values) => values,
292 // A page cannot be here: the line above just replaced it.
293 Store::Shared { .. } => unreachable!("a shared page was copied out one statement ago"),
294 }
295 }
296
297 /// Appends one value.
298 #[inline]
299 pub fn push(&mut self, value: T) {
300 self.to_mut().push(value);
301 }
302
303 /// Room for `additional` more values, taken in one allocation.
304 pub fn reserve(&mut self, additional: usize) {
305 self.to_mut().reserve(additional);
306 }
307
308 /// Appends a run of values.
309 pub fn extend_from_slice(&mut self, values: &[T]) {
310 self.to_mut().extend_from_slice(values);
311 }
312}
313
314/// Equality is the values and not where they live.
315///
316/// Derived equality would call an owned run different from a page holding the same values, which
317/// would make every test in the workspace that compares two vectors assert on how the vector was
318/// built. [`crate::StringColumn`] settles the same question the same way and for the same reason.
319impl<T: PartialEq> PartialEq for Buffer<T> {
320 fn eq(&self, other: &Self) -> bool {
321 self.as_slice() == other.as_slice()
322 }
323}
324
325impl<T: Eq> Eq for Buffer<T> {}
326
327impl<T> Default for Buffer<T> {
328 fn default() -> Self {
329 Self::new()
330 }
331}
332
333impl<T> Deref for Buffer<T> {
334 type Target = [T];
335
336 #[inline]
337 fn deref(&self) -> &[T] {
338 self.as_slice()
339 }
340}
341
342impl<T> From<Vec<T>> for Buffer<T> {
343 fn from(values: Vec<T>) -> Self {
344 Self::from_vec(values)
345 }
346}
347
348impl<T> FromIterator<T> for Buffer<T> {
349 fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
350 Self::from_vec(iter.into_iter().collect())
351 }
352}
353
354impl<T: Clone> Extend<T> for Buffer<T> {
355 fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
356 self.to_mut().extend(iter);
357 }
358}
359
360impl<T: Clone> IntoIterator for Buffer<T> {
361 type Item = T;
362 type IntoIter = std::vec::IntoIter<T>;
363
364 fn into_iter(self) -> Self::IntoIter {
365 self.into_vec().into_iter()
366 }
367}
368
369#[cfg(test)]
370mod tests {
371 use std::sync::Arc;
372
373 use super::{Buffer, Pin};
374
375 #[test]
376 fn a_buffer_reads_back_as_a_slice() {
377 let buffer: Buffer<i32> = vec![1, 2, 3].into();
378 assert_eq!(buffer.as_slice(), &[1, 2, 3]);
379 assert_eq!(buffer.len(), 3);
380 assert_eq!(buffer[1], 2);
381 assert_eq!(buffer.iter().sum::<i32>(), 6);
382 assert_eq!(buffer.clone().into_vec(), vec![1, 2, 3]);
383 }
384
385 #[test]
386 fn writing_goes_through_one_function() {
387 let mut buffer = Buffer::with_capacity(4);
388 buffer.push(1u8);
389 buffer.extend_from_slice(&[2, 3]);
390 buffer.extend([4u8]);
391 buffer.to_mut().sort_unstable_by(|a, b| b.cmp(a));
392 assert_eq!(buffer.as_slice(), &[4, 3, 2, 1]);
393 }
394
395 #[test]
396 fn an_empty_buffer_is_the_default_and_collects_like_a_vector() {
397 assert!(Buffer::<u64>::default().is_empty());
398 assert!(Buffer::<u64>::new().is_empty());
399 let collected: Buffer<u64> = (0..4).collect();
400 assert_eq!(collected.as_slice(), &[0, 1, 2, 3]);
401 assert_eq!(collected.into_iter().count(), 4);
402 }
403
404 #[test]
405 fn a_shared_page_reads_like_an_owned_run_and_compares_equal_to_one() {
406 let page = Arc::new(vec![1u8, 2, 3]);
407 let buffer = Buffer::from_arc(Arc::clone(&page));
408 assert!(buffer.is_shared());
409 assert_eq!(buffer.as_slice(), &[1, 2, 3]);
410 assert_eq!(buffer[2], 3);
411 assert_eq!(buffer, Buffer::from_vec(vec![1u8, 2, 3]));
412 assert_eq!(Buffer::from_vec(vec![1u8, 2, 3]), buffer);
413 assert_ne!(buffer, Buffer::from_vec(vec![1u8, 2]));
414 }
415
416 /// The point of the variant. The producer keeps a handle, the reader drops its buffer, and the
417 /// producer gets the allocation back rather than the allocator getting it.
418 #[test]
419 fn the_producer_gets_the_page_back_once_the_last_buffer_over_it_is_gone() {
420 let mut page = Arc::new(vec![0u8; 64]);
421 let address = page.as_ptr();
422 let first = Buffer::from_arc(Arc::clone(&page));
423 let second = Buffer::from_arc(Arc::clone(&page));
424 assert!(Arc::get_mut(&mut page).is_none());
425 drop(first);
426 assert!(Arc::get_mut(&mut page).is_none());
427 drop(second);
428 let run = Arc::get_mut(&mut page).expect("the last handle");
429 assert_eq!(run.as_ptr(), address, "the page was reallocated rather than reused");
430 }
431
432 /// Writing through a shared page copies it out, and the page the producer is holding is left
433 /// exactly as it was. The one case where getting this wrong would corrupt another reader.
434 #[test]
435 fn writing_through_a_shared_page_copies_it_and_leaves_the_page_alone() {
436 let page = Arc::new(vec![1u8, 2, 3]);
437 let mut buffer = Buffer::from_arc(Arc::clone(&page));
438 buffer.push(4);
439 assert!(!buffer.is_shared());
440 assert_eq!(buffer.as_slice(), &[1, 2, 3, 4]);
441 assert_eq!(page.as_slice(), &[1, 2, 3]);
442 assert_eq!(Buffer::from_arc(Arc::clone(&page)).into_vec(), vec![1, 2, 3]);
443 }
444
445 /// Taking the run out of the last handle to a page does not copy it, which is what makes the
446 /// shared variant free for a caller that ends up being the only reader after all.
447 #[test]
448 fn the_last_buffer_over_a_page_takes_the_run_without_copying_it() {
449 let page = Arc::new(vec![5u8; 32]);
450 let address = page.as_ptr();
451 let run = Buffer::from_arc(page).into_vec();
452 assert_eq!(run.as_ptr(), address);
453 }
454
455 /// The accounting rule from [`Buffer::footprint`], which is that the buffers over a page add up
456 /// to at most the page rather than to a multiple of it.
457 #[test]
458 fn a_shared_page_is_charged_once_across_the_buffers_over_it() {
459 let page = Arc::new(vec![0u64; 100]);
460 let over: Vec<_> = (0..4).map(|_| Buffer::from_arc(Arc::clone(&page))).collect();
461 let charged: usize = over.iter().map(Buffer::footprint).sum();
462 assert!(
463 charged <= page.capacity() * 8,
464 "{charged} charged for a {} byte page",
465 page.len() * 8
466 );
467 assert!(charged > 0);
468 assert_eq!(Buffer::from_vec(vec![0u64; 100]).footprint(), 800);
469 }
470
471 /// A window reads as its own run and nothing else, so nobody above the buffer can tell it is
472 /// looking at the middle of a page.
473 #[test]
474 fn a_window_reads_as_its_own_run() {
475 let page = Arc::new((0u8..10).collect::<Vec<_>>());
476 let window = Buffer::window(Arc::clone(&page), 3, 4);
477 assert!(window.is_shared());
478 assert_eq!(window.as_slice(), &[3, 4, 5, 6]);
479 assert_eq!(window.len(), 4);
480 assert_eq!(window[0], 3);
481 assert_eq!(window, Buffer::from_vec(vec![3u8, 4, 5, 6]));
482 assert_eq!(window.iter().sum::<u8>(), 18);
483 assert_eq!(window.clone().into_vec(), vec![3, 4, 5, 6]);
484 }
485
486 /// The whole point of the offset. Cutting a shared buffer moves a pointer and does not touch a
487 /// byte, which is asserted on the address rather than on the values, because the values would be
488 /// the same either way.
489 #[test]
490 fn cutting_a_shared_buffer_points_into_the_same_page() {
491 let page = Arc::new((0u64..100).collect::<Vec<_>>());
492 let address = page.as_ptr() as usize;
493 let whole = Buffer::from_arc(Arc::clone(&page));
494 let run = whole.slice(64, 16);
495 assert!(run.is_shared());
496 assert_eq!(run.as_slice().as_ptr() as usize, address + 64 * 8);
497 assert_eq!(run.as_slice(), &(64u64..80).collect::<Vec<_>>()[..]);
498 // And cutting a cut adds the offsets up rather than losing the first one.
499 let inner = run.slice(4, 2);
500 assert_eq!(inner.as_slice().as_ptr() as usize, address + 68 * 8);
501 assert_eq!(inner.as_slice(), &[68, 69]);
502 }
503
504 /// Cutting an owned run copies, which is the honest answer for a `Vec`, and the way out is to say
505 /// once that the run is a page.
506 #[test]
507 fn cutting_an_owned_run_copies_and_a_page_is_how_a_producer_avoids_that() {
508 let owned = Buffer::from_vec((0u32..16).collect());
509 let run = owned.slice(4, 4);
510 assert!(!run.is_shared());
511 assert_eq!(run.as_slice(), &[4, 5, 6, 7]);
512
513 let page = Buffer::from_vec((0u32..16).collect()).into_page();
514 let address = page.as_slice().as_ptr() as usize;
515 assert!(page.is_shared(), "into_page did not share the run");
516 let run = page.slice(4, 4);
517 assert!(run.is_shared());
518 assert_eq!(run.as_slice().as_ptr() as usize, address + 4 * 4);
519 assert_eq!(run.as_slice(), &[4, 5, 6, 7]);
520 // Twice is not two pages, and the second call does not lose an offset either.
521 let again = run.into_page();
522 assert_eq!(again.as_slice().as_ptr() as usize, address + 4 * 4);
523 }
524
525 /// A cut that runs off the end comes back short, because the callers that cut a buffer are
526 /// cutting it to a row count and a body is allowed to be shorter than the rows asked for.
527 #[test]
528 fn a_cut_past_the_end_comes_back_short_rather_than_panicking() {
529 let page = Arc::new(vec![1u16, 2, 3, 4]);
530 let whole = Buffer::from_arc(Arc::clone(&page));
531 assert_eq!(whole.slice(2, 10).as_slice(), &[3, 4]);
532 assert!(whole.slice(9, 1).is_empty());
533 assert_eq!(Buffer::window(Arc::clone(&page), 3, 9).as_slice(), &[4]);
534 assert!(Buffer::window(Arc::clone(&page), 7, 2).is_empty());
535 let owned = Buffer::from_vec(vec![1u16, 2, 3, 4]);
536 assert_eq!(owned.slice(2, 10).as_slice(), &[3, 4]);
537 assert!(owned.slice(9, 1).is_empty());
538 // A window of a window cannot see past the window it came from.
539 let middle = Buffer::window(Arc::clone(&page), 1, 2);
540 assert_eq!(middle.slice(0, 10).as_slice(), &[2, 3]);
541 }
542
543 /// Writing through a window copies the window and not the page, and leaves the page alone. The
544 /// case where getting it wrong would hand another reader somebody else's values.
545 #[test]
546 fn writing_through_a_window_copies_the_window_and_leaves_the_page_alone() {
547 let page = Arc::new(vec![1u8, 2, 3, 4, 5]);
548 let mut window = Buffer::window(Arc::clone(&page), 1, 3);
549 window.push(9);
550 assert!(!window.is_shared());
551 assert_eq!(window.as_slice(), &[2, 3, 4, 9]);
552 assert_eq!(page.as_slice(), &[1, 2, 3, 4, 5]);
553 }
554
555 /// Taking the run out of a window copies it, because the run it names is not a `Vec`, where the
556 /// whole page still comes out without a copy when this is the last handle to it.
557 #[test]
558 fn taking_the_run_out_of_a_window_copies_and_out_of_a_whole_page_does_not() {
559 let page = Arc::new(vec![7u8; 32]);
560 let address = page.as_ptr();
561 assert_eq!(Buffer::window(Arc::clone(&page), 8, 4).into_vec(), vec![7u8; 4]);
562 let whole = Buffer::from_arc(page).into_vec();
563 assert_eq!(whole.as_ptr(), address);
564 }
565
566 /// The accounting rule again, now that a buffer can be part of a page. The charge is the page's
567 /// share and not the window's, because the page is what is resident.
568 #[test]
569 fn a_window_is_charged_for_the_page_it_holds_down() {
570 let page = Arc::new(vec![0u64; 100]);
571 let windows: Vec<_> =
572 (0..4).map(|n| Buffer::window(Arc::clone(&page), n * 25, 25)).collect();
573 let charged: usize = windows.iter().map(Buffer::footprint).sum();
574 assert!(charged <= page.capacity() * 8, "{charged} charged for a {} byte page", 800);
575 assert!(charged > 100 * 8 / 4, "a window was charged less than its own share of the page");
576 }
577
578 /// The property the whole section 3.8 decision is about. A buffer of any payload can be sent to
579 /// another thread without a lifetime being involved, and so can a pin, which is what makes a
580 /// chunk `Send` once the borrowed variant exists. Asserted rather than assumed, because a pin
581 /// that was an `Rc` would compile everywhere else and fail here.
582 #[test]
583 fn a_buffer_and_a_pin_both_cross_a_thread_boundary() {
584 const fn assert_send<T: Send>() {}
585 assert_send::<Buffer<i64>>();
586 assert_send::<Pin>();
587 let pin: Pin = Arc::new(vec![0u8; 8]);
588 let buffer: Buffer<i64> = vec![7; 2].into();
589 let handle = std::thread::spawn(move || (buffer.len(), Arc::strong_count(&pin)));
590 assert_eq!(handle.join().expect("the thread"), (2, 1));
591 }
592}