1use std::collections::{vec_deque::IterMut, VecDeque};
5
6use allocative::Allocative;
7use linera_base::data_types::ArithmeticError;
8#[cfg(with_metrics)]
9use linera_base::prometheus_util::MeasureLatency as _;
10use serde::{de::DeserializeOwned, Deserialize, Serialize};
11
12use crate::{
13 batch::Batch,
14 common::{from_bytes_option, from_bytes_option_or_default, HasherOutput},
15 context::Context,
16 hashable_wrapper::WrappedHashableContainerView,
17 historical_hash_wrapper::HistoricallyHashableView,
18 store::ReadableKeyValueStore as _,
19 views::{ClonableView, HashableView, Hasher, View, ViewError, MIN_VIEW_TAG},
20};
21
22#[cfg(with_metrics)]
23pub(crate) mod metrics {
24 use linera_base::prometheus_util::{exponential_bucket_latencies, register_histogram_vec};
25 use prometheus::HistogramVec;
26
27 linera_base::declare_metrics! {
28 pub static BUCKET_QUEUE_VIEW_HASH_RUNTIME: HistogramVec =
30 register_histogram_vec(
31 "bucket_queue_view_hash_runtime",
32 "BucketQueueView hash runtime",
33 &[],
34 exponential_bucket_latencies(5.0),
35 );
36 }
37}
38
39#[repr(u8)]
41enum KeyTag {
42 Layout = MIN_VIEW_TAG,
44 Front,
46 Middle,
48 Back,
50}
51
52#[derive(Clone, Debug, Default, Serialize, Deserialize)]
58struct BucketLayout {
59 front_position: u32,
61 num_buckets: u32,
63 first_index: u32,
66}
67
68#[derive(Copy, Clone, Debug, Allocative)]
70struct Cursor {
71 offset: usize,
74 position: usize,
76}
77
78#[derive(Debug, Allocative)]
90#[allocative(bound = "C, T: Allocative, const N: usize")]
91pub struct BucketQueueView<C, T, const N: usize> {
92 #[allocative(skip)]
94 context: C,
95 new_back_values: VecDeque<T>,
97 stored_first_index: u32,
99 stored_num_buckets: u32,
101 stored_front: Vec<T>,
108 stored_back: Vec<T>,
111 stored_front_position: u32,
113 cursor: Option<Cursor>,
120 current_middle: Option<Vec<T>>,
124 delete_storage_first: bool,
126}
127
128impl<C, T, const N: usize> View for BucketQueueView<C, T, N>
129where
130 C: Context,
131 T: Send + Sync + Clone + Serialize + DeserializeOwned,
132{
133 const NUM_INIT_KEYS: usize = 3;
134
135 type Context = C;
136
137 fn context(&self) -> C {
138 self.context.clone()
139 }
140
141 fn pre_load(context: &C) -> Result<Vec<Vec<u8>>, ViewError> {
142 let key1 = context.base_key().base_tag(KeyTag::Layout as u8);
143 let key2 = context.base_key().base_tag(KeyTag::Front as u8);
144 let key3 = context.base_key().base_tag(KeyTag::Back as u8);
145 Ok(vec![key1, key2, key3])
146 }
147
148 fn post_load(context: C, values: &[Option<Vec<u8>>]) -> Result<Self, ViewError> {
149 let value_layout = values.first().ok_or(ViewError::PostLoadValuesError)?;
150 let value_front = values.get(1).ok_or(ViewError::PostLoadValuesError)?;
151 let value_back = values.get(2).ok_or(ViewError::PostLoadValuesError)?;
152 let front = from_bytes_option::<Vec<T>>(value_front)?;
153 let back = from_bytes_option::<Vec<T>>(value_back)?;
154 let layout = from_bytes_option_or_default::<BucketLayout>(value_layout)?;
155 let (stored_first_index, stored_num_buckets, stored_front, stored_back, cursor) =
158 match front {
159 Some(front) => {
160 let back = if layout.num_buckets >= 2 {
161 back.unwrap_or_default()
162 } else {
163 Vec::new()
164 };
165 let cursor = Cursor {
166 offset: 0,
167 position: layout.front_position as usize,
168 };
169 (
170 layout.first_index,
171 layout.num_buckets,
172 front,
173 back,
174 Some(cursor),
175 )
176 }
177 None => (0, 0, Vec::new(), Vec::new(), None),
178 };
179 Ok(Self {
180 context,
181 new_back_values: VecDeque::new(),
182 stored_first_index,
183 stored_num_buckets,
184 stored_front,
185 stored_back,
186 stored_front_position: layout.front_position,
187 cursor,
188 current_middle: None,
189 delete_storage_first: false,
190 })
191 }
192
193 fn rollback(&mut self) {
194 self.delete_storage_first = false;
199 self.cursor = (self.stored_num_buckets > 0).then_some(Cursor {
200 offset: 0,
201 position: self.stored_front_position as usize,
202 });
203 self.current_middle = None;
204 self.new_back_values.clear();
205 }
206
207 async fn has_pending_changes(&self) -> bool {
208 if self.delete_storage_first {
209 return true;
210 }
211 if self.stored_num_buckets > 0 {
212 let Some(cursor) = self.cursor else {
213 return true;
214 };
215 if cursor.offset != 0 || cursor.position != self.stored_front_position as usize {
216 return true;
217 }
218 }
219 !self.new_back_values.is_empty()
220 }
221
222 fn pre_save(&self, batch: &mut Batch) -> Result<bool, ViewError> {
223 let plan = self.save_plan()?;
224 match plan.case {
225 SaveCase::Empty => {
226 if plan.has_storage {
227 batch.delete_key_prefix(self.context.base_key().bytes.clone());
228 }
229 Ok(true)
230 }
231 SaveCase::MetadataOnly => {
232 batch.put_key_value(
233 self.layout_key(),
234 &BucketLayout {
235 front_position: plan.cursor_position,
236 num_buckets: 1,
237 first_index: self.stored_first_index,
238 },
239 )?;
240 Ok(false)
241 }
242 SaveCase::Rewrite => {
243 if plan.has_storage {
244 batch.delete_key_prefix(self.context.base_key().bytes.clone());
245 }
246 let mut all_data = Vec::new();
247 if let Some(data) = self.current_front_data() {
248 all_data.extend(data[plan.cursor_position as usize..].iter().cloned());
249 }
250 all_data.extend(self.new_back_values.iter().cloned());
251 if all_data.is_empty() {
252 return Ok(true);
253 }
254 let first_index = 0;
255 let num_buckets = self.write_chunks(batch, &all_data, first_index)?;
256 batch.put_key_value(
257 self.layout_key(),
258 &BucketLayout {
259 front_position: 0,
260 num_buckets,
261 first_index,
262 },
263 )?;
264 Ok(false)
265 }
266 SaveCase::Patch {
267 new_first_index,
268 remaining_count,
269 } => {
270 for offset in 1..plan.remaining_offset {
275 let index = checked_bucket_index(self.stored_first_index, offset)?;
276 batch.delete_key(self.get_middle_key(index)?);
277 }
278 if plan.remaining_offset > 0 {
280 let data = self
281 .current_front_data()
282 .expect("Patch implies a live cursor within the stored buckets");
283 batch.put_key_value(self.front_key(), &data.to_vec())?;
284 batch.delete_key(self.get_middle_key(new_first_index)?);
285 }
286
287 let num_buckets = if self.new_back_values.is_empty() {
288 remaining_count
289 } else {
290 let mut merged = self.stored_back.clone();
292 merged.extend(self.new_back_values.iter().cloned());
293 let chunks = merged.chunks(N).collect::<Vec<_>>();
294 let num_new_chunks =
295 u32::try_from(chunks.len()).map_err(|_| ArithmeticError::Overflow)?;
296 let new_middle_start =
299 checked_bucket_index(new_first_index, remaining_count as usize - 1)?;
300 for (i, chunk) in chunks.iter().enumerate().take(chunks.len() - 1) {
301 let key =
302 self.get_middle_key(checked_bucket_index(new_middle_start, i)?)?;
303 batch.put_key_value(key, &chunk.to_vec())?;
304 }
305 batch.put_key_value(self.back_key(), &chunks.last().unwrap().to_vec())?;
306 (remaining_count - 1)
307 .checked_add(num_new_chunks)
308 .ok_or(ArithmeticError::Overflow)?
309 };
310
311 batch.put_key_value(
312 self.layout_key(),
313 &BucketLayout {
314 front_position: plan.cursor_position,
315 num_buckets,
316 first_index: new_first_index,
317 },
318 )?;
319 Ok(false)
320 }
321 }
322 }
323
324 fn post_save(&mut self) {
325 let plan = self.save_plan().expect("verified in pre_save");
326 self.delete_storage_first = false;
327 match plan.case {
328 SaveCase::Empty => {
329 self.stored_first_index = 0;
330 self.stored_num_buckets = 0;
331 self.stored_front = Vec::new();
332 self.stored_back = Vec::new();
333 self.cursor = None;
334 self.current_middle = None;
335 self.stored_front_position = 0;
336 }
337 SaveCase::MetadataOnly => {
338 self.cursor = Some(Cursor {
340 offset: 0,
341 position: plan.cursor_position as usize,
342 });
343 self.current_middle = None;
344 self.stored_front_position = plan.cursor_position;
345 }
346 SaveCase::Rewrite => {
347 let mut all_data = Vec::new();
348 if let Some(data) = self.current_front_data() {
349 all_data.extend(data[plan.cursor_position as usize..].iter().cloned());
350 }
351 all_data.extend(std::mem::take(&mut self.new_back_values));
352 let num_chunks = all_data.chunks(N).len();
355 self.stored_first_index = 0;
356 self.stored_num_buckets = u32::try_from(num_chunks).expect("verified in pre_save");
357 self.current_middle = None;
358 self.stored_front_position = 0;
359 if num_chunks == 0 {
360 self.stored_front = Vec::new();
361 self.stored_back = Vec::new();
362 self.cursor = None;
363 } else {
364 self.stored_back = if num_chunks >= 2 {
365 all_data[(num_chunks - 1) * N..].to_vec()
366 } else {
367 Vec::new()
368 };
369 all_data.truncate(N); self.stored_front = all_data;
371 self.cursor = Some(Cursor {
372 offset: 0,
373 position: 0,
374 });
375 }
376 }
377 SaveCase::Patch {
378 new_first_index,
379 remaining_count,
380 } => {
381 let cursor = self.cursor.expect("Patch implies a live cursor");
382 if plan.remaining_offset > 0 {
386 self.stored_front = self
387 .current_middle
388 .take()
389 .expect("the middle the cursor points into is loaded");
390 }
391 self.current_middle = None;
392 self.stored_first_index = new_first_index;
393 self.stored_num_buckets = if self.new_back_values.is_empty() {
394 remaining_count
395 } else {
396 let mut merged = std::mem::take(&mut self.stored_back);
399 merged.extend(std::mem::take(&mut self.new_back_values));
400 let num_new_chunks =
401 u32::try_from(merged.chunks(N).len()).expect("verified in pre_save");
402 let back_start = (num_new_chunks as usize - 1) * N;
403 self.stored_back = merged.split_off(back_start);
404 (remaining_count - 1) + num_new_chunks
405 };
406 self.cursor = Some(Cursor {
407 offset: 0,
408 position: cursor.position,
409 });
410 self.stored_front_position = plan.cursor_position;
411 }
412 }
413 }
414
415 fn clear(&mut self) {
416 self.delete_storage_first = true;
419 self.new_back_values.clear();
420 self.cursor = None;
421 self.current_middle = None;
422 }
423}
424
425impl<C: Clone, T: Clone, const N: usize> ClonableView for BucketQueueView<C, T, N>
426where
427 Self: View,
428{
429 fn clone_unchecked(&mut self) -> Result<Self, ViewError> {
430 Ok(BucketQueueView {
431 context: self.context.clone(),
432 new_back_values: self.new_back_values.clone(),
433 stored_first_index: self.stored_first_index,
434 stored_num_buckets: self.stored_num_buckets,
435 stored_front: self.stored_front.clone(),
436 stored_back: self.stored_back.clone(),
437 stored_front_position: self.stored_front_position,
438 cursor: self.cursor,
439 current_middle: self.current_middle.clone(),
440 delete_storage_first: self.delete_storage_first,
441 })
442 }
443}
444
445#[derive(Debug)]
449enum SaveCase {
450 Empty,
452 MetadataOnly,
455 Rewrite,
458 Patch {
462 new_first_index: u32,
463 remaining_count: u32,
464 },
465}
466
467#[derive(Debug)]
468struct SavePlan {
469 case: SaveCase,
470 remaining_offset: usize,
473 cursor_position: u32,
477 has_storage: bool,
479}
480
481fn checked_bucket_index(base: u32, offset: usize) -> Result<u32, ArithmeticError> {
487 let offset = u32::try_from(offset).map_err(|_| ArithmeticError::Overflow)?;
488 base.checked_add(offset).ok_or(ArithmeticError::Overflow)
489}
490
491impl<C: Context, T, const N: usize> BucketQueueView<C, T, N> {
492 fn front_key(&self) -> Vec<u8> {
493 self.context.base_key().base_tag(KeyTag::Front as u8)
494 }
495
496 fn back_key(&self) -> Vec<u8> {
497 self.context.base_key().base_tag(KeyTag::Back as u8)
498 }
499
500 fn layout_key(&self) -> Vec<u8> {
501 self.context.base_key().base_tag(KeyTag::Layout as u8)
502 }
503
504 fn get_middle_key(&self, index: u32) -> Result<Vec<u8>, ViewError> {
506 Ok(self
507 .context
508 .base_key()
509 .derive_tag_key(KeyTag::Middle as u8, &index)?)
510 }
511
512 fn current_front_data(&self) -> Option<&[T]> {
515 let cursor = self.cursor?;
516 let num_buckets = self.stored_num_buckets as usize;
517 Some(if cursor.offset == 0 {
518 &self.stored_front
519 } else if cursor.offset + 1 == num_buckets {
520 &self.stored_back
521 } else {
522 self.current_middle
523 .as_deref()
524 .expect("the middle bucket the cursor points into is loaded")
525 })
526 }
527
528 fn bucket_len(&self, offset: usize) -> usize {
531 if offset == 0 {
532 self.stored_front.len()
533 } else if offset + 1 == self.stored_num_buckets as usize {
534 self.stored_back.len()
535 } else {
536 N
537 }
538 }
539
540 fn save_plan(&self) -> Result<SavePlan, ViewError> {
544 let num_buckets = self.stored_num_buckets as usize;
545 let remaining_offset = if self.delete_storage_first {
546 num_buckets
547 } else {
548 self.cursor.map_or(num_buckets, |c| c.offset)
549 };
550 let remaining_count = num_buckets - remaining_offset;
551 let cursor_position = u32::try_from(self.cursor.map_or(0, |c| c.position))
552 .map_err(|_| ArithmeticError::Overflow)?;
553 let has_storage = self.stored_num_buckets > 0 || self.delete_storage_first;
554 let new_back_empty = self.new_back_values.is_empty();
555
556 let case = if remaining_count == 0 && new_back_empty {
557 SaveCase::Empty
558 } else if remaining_count == 1 && remaining_offset == 0 && new_back_empty {
559 SaveCase::MetadataOnly
560 } else if remaining_count <= 1 {
561 SaveCase::Rewrite
562 } else {
563 SaveCase::Patch {
564 new_first_index: checked_bucket_index(self.stored_first_index, remaining_offset)?,
565 remaining_count: u32::try_from(remaining_count)
566 .map_err(|_| ArithmeticError::Overflow)?,
567 }
568 };
569 Ok(SavePlan {
570 case,
571 remaining_offset,
572 cursor_position,
573 has_storage,
574 })
575 }
576
577 fn write_chunks(
582 &self,
583 batch: &mut Batch,
584 data: &[T],
585 first_index: u32,
586 ) -> Result<u32, ViewError>
587 where
588 T: Serialize + Clone,
589 {
590 let chunks = data.chunks(N).collect::<Vec<_>>();
591 let num_buckets = u32::try_from(chunks.len()).map_err(|_| ArithmeticError::Overflow)?;
592 batch.put_key_value(self.front_key(), &chunks[0].to_vec())?;
593 for (i, chunk) in chunks
594 .iter()
595 .enumerate()
596 .skip(1)
597 .take(chunks.len().saturating_sub(2))
598 {
599 let key = self.get_middle_key(checked_bucket_index(first_index, i)?)?;
600 batch.put_key_value(key, &chunk.to_vec())?;
601 }
602 if num_buckets >= 2 {
603 batch.put_key_value(self.back_key(), &chunks.last().unwrap().to_vec())?;
604 }
605 Ok(num_buckets)
606 }
607
608 fn stored_count(&self) -> usize {
610 if self.delete_storage_first {
611 return 0;
612 }
613 let Some(cursor) = self.cursor else {
614 return 0;
615 };
616 let remaining = self.stored_num_buckets as usize - cursor.offset;
617 let front_count = self.bucket_len(cursor.offset) - cursor.position;
619 if remaining == 1 {
620 return front_count;
621 }
622 let back_count = self.stored_back.len();
624 let num_middles = remaining - 2;
625 front_count + num_middles * N + back_count
626 }
627
628 pub fn count(&self) -> usize {
641 self.stored_count() + self.new_back_values.len()
642 }
643}
644
645impl<C: Context, T: DeserializeOwned + Clone, const N: usize> BucketQueueView<C, T, N> {
646 pub fn front(&self) -> Option<&T> {
660 match self.cursor {
661 Some(cursor) => {
662 let data = self
663 .current_front_data()
664 .expect("cursor is Some, so the current front is available");
665 Some(&data[cursor.position])
666 }
667 None => self.new_back_values.front(),
668 }
669 }
670
671 pub fn front_mut(&mut self) -> Option<&mut T> {
687 match self.cursor {
688 Some(cursor) => {
689 let num_buckets = self.stored_num_buckets as usize;
690 let data = if cursor.offset == 0 {
691 &mut self.stored_front
692 } else if cursor.offset + 1 == num_buckets {
693 &mut self.stored_back
694 } else {
695 self.current_middle
696 .as_mut()
697 .expect("the middle bucket the cursor points into is loaded")
698 };
699 Some(
700 data.get_mut(cursor.position)
701 .expect("cursor.position must be a valid position within the front bucket"),
702 )
703 }
704 None => self.new_back_values.front_mut(),
705 }
706 }
707
708 pub async fn delete_front(&mut self) -> Result<(), ViewError> {
722 let Some(cursor) = self.cursor else {
723 self.new_back_values.pop_front();
724 return Ok(());
725 };
726 let current_len = self
727 .current_front_data()
728 .expect("cursor points into the stored buckets")
729 .len();
730 let num_buckets = self.stored_num_buckets as usize;
731 let mut offset = cursor.offset;
732 let mut position = cursor.position + 1;
733 if position == current_len {
734 offset += 1;
735 position = 0;
736 if offset == num_buckets {
737 self.cursor = None;
739 self.current_middle = None;
740 return Ok(());
741 }
742 if offset + 1 == num_buckets {
746 self.current_middle = None;
748 } else {
749 let index = checked_bucket_index(self.stored_first_index, offset)?;
750 let key = self.get_middle_key(index)?;
751 let data = self
752 .context
753 .store()
754 .read_value(&key)
755 .await?
756 .ok_or_else(|| {
757 ViewError::MissingEntries("BucketQueueView::delete_front".into())
758 })?;
759 self.current_middle = Some(data);
760 }
761 }
762 self.cursor = Some(Cursor { offset, position });
763 Ok(())
764 }
765
766 pub fn push_back(&mut self, value: T) {
779 self.new_back_values.push_back(value);
780 }
781
782 pub async fn elements(&self) -> Result<Vec<T>, ViewError> {
796 let count = self.count();
797 self.read_context(self.cursor, count).await
798 }
799
800 pub async fn back(&mut self) -> Result<Option<T>, ViewError>
814 where
815 T: Clone,
816 {
817 if let Some(value) = self.new_back_values.back() {
818 return Ok(Some(value.clone()));
819 }
820 if self.cursor.is_none() {
821 return Ok(None);
822 }
823 let last = if self.stored_num_buckets >= 2 {
826 self.stored_back.last()
827 } else {
828 self.stored_front.last()
829 };
830 Ok(last.cloned())
831 }
832
833 async fn read_context(
834 &self,
835 cursor: Option<Cursor>,
836 count: usize,
837 ) -> Result<Vec<T>, ViewError> {
838 if count == 0 {
839 return Ok(Vec::new());
840 }
841 let mut elements = Vec::<T>::new();
842 let mut count_remain = count;
843 if let Some(cursor) = cursor {
844 let num_buckets = self.stored_num_buckets as usize;
845 let mut keys = Vec::new();
847 let mut position = cursor.position;
848 let mut remain = count;
849 for offset in cursor.offset..num_buckets {
850 if offset != 0 && offset + 1 != num_buckets {
851 let index = checked_bucket_index(self.stored_first_index, offset)?;
852 keys.push(self.get_middle_key(index)?);
853 }
854 let size = self.bucket_len(offset) - position;
855 if size >= remain {
856 break;
857 }
858 remain -= size;
859 position = 0;
860 }
861 let values = self.context.store().read_multi_values_bytes(&keys).await?;
862 let mut value_pos = 0;
864 let mut position = cursor.position;
865 for offset in cursor.offset..num_buckets {
866 let read_buf;
867 let data: &[T] = if offset == 0 {
868 &self.stored_front
869 } else if offset + 1 == num_buckets {
870 &self.stored_back
871 } else {
872 let value = values[value_pos].as_ref().ok_or_else(|| {
873 ViewError::MissingEntries("BucketQueueView::read_context".into())
874 })?;
875 value_pos += 1;
876 read_buf = bcs::from_bytes::<Vec<T>>(value)?;
877 &read_buf
878 };
879 let size = data.len() - position;
880 elements.extend(data[position..].iter().take(count_remain).cloned());
881 if size >= count_remain {
882 return Ok(elements);
883 }
884 count_remain -= size;
885 position = 0;
886 }
887 }
888 let count_read = std::cmp::min(count_remain, self.new_back_values.len());
889 elements.extend(self.new_back_values.range(0..count_read).cloned());
890 Ok(elements)
891 }
892
893 pub async fn read_front(&self, count: usize) -> Result<Vec<T>, ViewError> {
908 let count = std::cmp::min(count, self.count());
909 self.read_context(self.cursor, count).await
910 }
911
912 pub async fn read_back(&self, count: usize) -> Result<Vec<T>, ViewError> {
927 let count = std::cmp::min(count, self.count());
928 if count <= self.new_back_values.len() {
929 let start = self.new_back_values.len() - count;
930 Ok(self
931 .new_back_values
932 .range(start..)
933 .cloned()
934 .collect::<Vec<_>>())
935 } else {
936 let mut increment = self.count() - count;
937 let Some(cursor) = self.cursor else {
938 unreachable!("Cursor should be Some when stored_count > 0");
939 };
940 let num_buckets = self.stored_num_buckets as usize;
941 let mut position = cursor.position;
942 for offset in cursor.offset..num_buckets {
943 let size = self.bucket_len(offset) - position;
944 if increment < size {
945 return self
946 .read_context(
947 Some(Cursor {
948 offset,
949 position: position + increment,
950 }),
951 count,
952 )
953 .await;
954 }
955 increment -= size;
956 position = 0;
957 }
958 unreachable!("BucketQueueView::read_back: iterated past all stored buckets without finding the requested position");
959 }
960 }
961
962 async fn load_all(&mut self) -> Result<(), ViewError> {
963 if !self.delete_storage_first {
964 let elements = self.elements().await?;
965 self.new_back_values.clear();
966 for elt in elements {
967 self.new_back_values.push_back(elt);
968 }
969 self.cursor = None;
970 self.current_middle = None;
971 self.delete_storage_first = true;
972 }
973 Ok(())
974 }
975
976 pub async fn try_iter_mut(&mut self) -> Result<IterMut<'_, T>, ViewError> {
992 self.load_all().await?;
993 Ok(self.new_back_values.iter_mut())
994 }
995}
996
997impl<C: Context, T: Serialize + DeserializeOwned + Send + Sync + Clone, const N: usize> HashableView
998 for BucketQueueView<C, T, N>
999where
1000 Self: View,
1001{
1002 type Hasher = sha3::Sha3_256;
1003
1004 async fn hash_mut(&mut self) -> Result<<Self::Hasher as Hasher>::Output, ViewError> {
1005 self.hash().await
1006 }
1007
1008 async fn hash(&self) -> Result<<Self::Hasher as Hasher>::Output, ViewError> {
1009 #[cfg(with_metrics)]
1010 let _hash_latency = metrics::BUCKET_QUEUE_VIEW_HASH_RUNTIME.measure_latency();
1011 let elements = self.elements().await?;
1012 let mut hasher = sha3::Sha3_256::default();
1013 hasher.update_with_bcs_bytes(&elements)?;
1014 Ok(hasher.finalize())
1015 }
1016}
1017
1018pub type HashedBucketQueueView<C, T, const N: usize> =
1020 WrappedHashableContainerView<C, BucketQueueView<C, T, N>, HasherOutput>;
1021
1022pub type HistoricallyHashedBucketQueueView<C, T, const N: usize> =
1024 HistoricallyHashableView<C, BucketQueueView<C, T, N>>;
1025
1026#[cfg(with_graphql)]
1027mod graphql {
1028 use std::borrow::Cow;
1029
1030 use linera_base::data_types::ArithmeticError;
1031
1032 use super::BucketQueueView;
1033 use crate::{
1034 context::Context,
1035 graphql::{hash_name, mangle},
1036 };
1037
1038 impl<C: Send + Sync, T: async_graphql::OutputType, const N: usize> async_graphql::TypeName
1039 for BucketQueueView<C, T, N>
1040 {
1041 fn type_name() -> Cow<'static, str> {
1042 format!(
1043 "BucketQueueView_{}_{:08x}",
1044 mangle(T::type_name()),
1045 hash_name::<T>()
1046 )
1047 .into()
1048 }
1049 }
1050
1051 #[async_graphql::Object(cache_control(no_cache), name_type)]
1052 impl<C: Context, T: async_graphql::OutputType, const N: usize> BucketQueueView<C, T, N>
1053 where
1054 C: Send + Sync,
1055 T: serde::ser::Serialize + serde::de::DeserializeOwned + Clone + Send + Sync,
1056 {
1057 #[graphql(derived(name = "count"))]
1058 async fn count_(&self) -> Result<u32, async_graphql::Error> {
1059 Ok(u32::try_from(self.count()).map_err(|_| ArithmeticError::Overflow)?)
1060 }
1061
1062 async fn entries(&self, count: Option<usize>) -> async_graphql::Result<Vec<T>> {
1063 Ok(self
1064 .read_front(count.unwrap_or_else(|| self.count()))
1065 .await?)
1066 }
1067 }
1068}
1069
1070#[cfg(test)]
1071mod tests {
1072 use super::*;
1073 use crate::{
1074 batch::Batch,
1075 context::{Context, MemoryContext},
1076 store::WritableKeyValueStore as _,
1077 };
1078
1079 #[tokio::test]
1084 async fn delete_front_load_failure_preserves_invariant() -> Result<(), ViewError> {
1085 let context = MemoryContext::new_for_testing(());
1086 let mut view = BucketQueueView::<_, u8, 2>::load(context.clone()).await?;
1087 for value in [1u8, 2, 3, 4, 5, 6] {
1089 view.push_back(value);
1090 }
1091 save(&context, &mut view).await?;
1092
1093 let mut view = BucketQueueView::<_, u8, 2>::load(context.clone()).await?;
1094
1095 let middle_key = view.get_middle_key(1)?;
1097 let mut batch = Batch::new();
1098 batch.delete_key(middle_key);
1099 context.store().write_batch(batch).await?;
1100
1101 view.delete_front().await?;
1102 let err = view.delete_front().await.expect_err("load should fail");
1103 assert!(matches!(err, ViewError::MissingEntries(_)));
1104
1105 save(&context, &mut view).await?;
1106
1107 Ok(())
1108 }
1109
1110 #[tokio::test]
1115 async fn save_load_roundtrip_across_sizes() -> Result<(), ViewError> {
1116 const N: usize = 3;
1117 for size in [0usize, 1, 2, N, N + 1, 2 * N, 2 * N + 1, 5 * N, 5 * N - 1] {
1118 let context = MemoryContext::new_for_testing(());
1119 let mut view = BucketQueueView::<_, u32, N>::load(context.clone()).await?;
1120 for i in 0..u32::try_from(size).unwrap() {
1121 view.push_back(i);
1122 }
1123 save(&context, &mut view).await?;
1124
1125 let reloaded = BucketQueueView::<_, u32, N>::load(context).await?;
1126 let elements = reloaded.elements().await?;
1127 let expected = (0..u32::try_from(size).unwrap()).collect::<Vec<_>>();
1128 assert_eq!(elements, expected, "size = {size}");
1129 assert_eq!(reloaded.count(), size, "count for size = {size}");
1130 }
1131 Ok(())
1132 }
1133
1134 #[tokio::test]
1138 async fn middle_buckets_are_always_full() -> Result<(), ViewError> {
1139 const N: usize = 4;
1140 let context = MemoryContext::new_for_testing(());
1141 let mut view = BucketQueueView::<_, u32, N>::load(context.clone()).await?;
1142 for i in 0..u32::try_from(5 * N + 2).unwrap() {
1144 view.push_back(i);
1145 }
1146 save(&context, &mut view).await?;
1147
1148 view.push_back(1000);
1150 view.push_back(1001);
1151 save(&context, &mut view).await?;
1152
1153 for _ in 0..(N - 1) {
1155 view.delete_front().await?;
1156 }
1157 save(&context, &mut view).await?;
1158
1159 let view = BucketQueueView::<_, u32, N>::load(context.clone()).await?;
1161 let first_index = view.stored_first_index;
1162 for offset in 1..view.stored_num_buckets.saturating_sub(1) {
1163 let key = view.get_middle_key(first_index + offset)?;
1164 let data = context
1165 .store()
1166 .read_value::<Vec<u32>>(&key)
1167 .await?
1168 .expect("middle bucket should be present in storage");
1169 assert_eq!(
1170 data.len(),
1171 N,
1172 "middle at offset {offset} should hold N elements"
1173 );
1174 }
1175 Ok(())
1176 }
1177
1178 #[tokio::test]
1181 async fn stored_count_is_exact() -> Result<(), ViewError> {
1182 const N: usize = 3;
1183 let context = MemoryContext::new_for_testing(());
1184 let mut view = BucketQueueView::<_, u32, N>::load(context.clone()).await?;
1185 for i in 0..7u32 {
1187 view.push_back(i);
1188 }
1189 save(&context, &mut view).await?;
1190
1191 let mut view = BucketQueueView::<_, u32, N>::load(context).await?;
1192 assert_eq!(view.stored_count(), 7);
1193 view.delete_front().await?; assert_eq!(view.stored_count(), 6);
1195 view.delete_front().await?; assert_eq!(view.stored_count(), 5);
1197 view.delete_front().await?; assert_eq!(view.stored_count(), 4);
1199 Ok(())
1200 }
1201
1202 #[tokio::test]
1207 async fn save_plan_covers_each_case() -> Result<(), ViewError> {
1208 const N: usize = 3;
1209
1210 let context = MemoryContext::new_for_testing(());
1212 let view = BucketQueueView::<_, u32, N>::load(context).await?;
1213 assert!(matches!(view.save_plan()?.case, SaveCase::Empty));
1214
1215 let context = MemoryContext::new_for_testing(());
1217 let mut view = BucketQueueView::<_, u32, N>::load(context.clone()).await?;
1218 view.push_back(10);
1219 view.push_back(20);
1220 save(&context, &mut view).await?;
1221 let mut view = BucketQueueView::<_, u32, N>::load(context).await?;
1222 view.delete_front().await?;
1223 assert!(matches!(view.save_plan()?.case, SaveCase::MetadataOnly));
1224
1225 let context = MemoryContext::new_for_testing(());
1228 let mut view = BucketQueueView::<_, u32, N>::load(context.clone()).await?;
1229 for i in 0..5u32 {
1230 view.push_back(i);
1231 }
1232 save(&context, &mut view).await?;
1233 let mut view = BucketQueueView::<_, u32, N>::load(context).await?;
1234 for _ in 0..N {
1235 view.delete_front().await?;
1236 }
1237 assert!(matches!(view.save_plan()?.case, SaveCase::Rewrite));
1238
1239 let context = MemoryContext::new_for_testing(());
1242 let mut view = BucketQueueView::<_, u32, N>::load(context.clone()).await?;
1243 for i in 0..7u32 {
1244 view.push_back(i);
1245 }
1246 save(&context, &mut view).await?;
1247 let mut view = BucketQueueView::<_, u32, N>::load(context).await?;
1248 view.push_back(100);
1249 assert!(matches!(view.save_plan()?.case, SaveCase::Patch { .. }));
1250
1251 Ok(())
1252 }
1253
1254 #[tokio::test]
1261 async fn patch_promotes_front_keeps_middles_and_rechunks() -> Result<(), ViewError> {
1262 const N: usize = 3;
1263 let context = MemoryContext::new_for_testing(());
1264 let mut view = BucketQueueView::<_, u32, N>::load(context.clone()).await?;
1265 for i in 0..14u32 {
1267 view.push_back(i);
1268 }
1269 save(&context, &mut view).await?;
1270
1271 let mut view = BucketQueueView::<_, u32, N>::load(context.clone()).await?;
1272 for _ in 0..4 {
1275 view.delete_front().await?;
1276 }
1277 for v in 100..106u32 {
1280 view.push_back(v);
1281 }
1282
1283 let plan = view.save_plan()?;
1286 assert_eq!(plan.remaining_offset, 1);
1287 assert!(matches!(
1288 plan.case,
1289 SaveCase::Patch {
1290 remaining_count: 4,
1291 ..
1292 }
1293 ));
1294
1295 save(&context, &mut view).await?;
1296
1297 let view = BucketQueueView::<_, u32, N>::load(context.clone()).await?;
1299 let expected: Vec<u32> = (4..14).chain(100..106).collect();
1300 assert_eq!(view.elements().await?, expected);
1301 assert_eq!(view.count(), expected.len());
1302
1303 assert_eq!(view.stored_num_buckets, 6);
1306 let first_index = view.stored_first_index;
1307 for offset in 1..view.stored_num_buckets - 1 {
1308 let key = view.get_middle_key(first_index + offset)?;
1309 let data = context
1310 .store()
1311 .read_value::<Vec<u32>>(&key)
1312 .await?
1313 .expect("middle bucket should be present in storage");
1314 assert_eq!(data.len(), N, "middle at offset {offset} must hold N");
1315 }
1316 Ok(())
1317 }
1318
1319 #[tokio::test]
1323 async fn n_equals_one_roundtrip() -> Result<(), ViewError> {
1324 let context = MemoryContext::new_for_testing(());
1325 let mut view = BucketQueueView::<_, u32, 1>::load(context.clone()).await?;
1326 for i in 0..5u32 {
1327 view.push_back(i);
1328 }
1329 save(&context, &mut view).await?;
1330
1331 let mut view = BucketQueueView::<_, u32, 1>::load(context.clone()).await?;
1332 assert_eq!(view.elements().await?, vec![0, 1, 2, 3, 4]);
1333 view.delete_front().await?;
1334 view.delete_front().await?;
1335 view.push_back(99);
1336 save(&context, &mut view).await?;
1337
1338 let view = BucketQueueView::<_, u32, 1>::load(context).await?;
1339 assert_eq!(view.elements().await?, vec![2, 3, 4, 99]);
1340 Ok(())
1341 }
1342
1343 #[tokio::test]
1346 async fn rollback_restores_saved_state() -> Result<(), ViewError> {
1347 const N: usize = 3;
1348 let context = MemoryContext::new_for_testing(());
1349 let mut view = BucketQueueView::<_, u32, N>::load(context.clone()).await?;
1350 for i in 0..5u32 {
1351 view.push_back(i);
1352 }
1353 save(&context, &mut view).await?;
1354
1355 let mut view = BucketQueueView::<_, u32, N>::load(context).await?;
1356 view.delete_front().await?;
1357 view.delete_front().await?;
1358 view.push_back(100);
1359 view.push_back(101);
1360 assert_eq!(view.elements().await?, vec![2, 3, 4, 100, 101]);
1361
1362 view.rollback();
1363 assert_eq!(view.elements().await?, vec![0, 1, 2, 3, 4]);
1364
1365 assert!(!view.has_pending_changes().await);
1367 Ok(())
1368 }
1369
1370 #[tokio::test]
1375 async fn rollback_after_clear_restores_front_position() -> Result<(), ViewError> {
1376 const N: usize = 5;
1377 let context = MemoryContext::new_for_testing(());
1378 let mut view = BucketQueueView::<_, u32, N>::load(context.clone()).await?;
1379 for value in [10u32, 20, 30] {
1380 view.push_back(value);
1381 }
1382 save(&context, &mut view).await?;
1383
1384 let mut view = BucketQueueView::<_, u32, N>::load(context.clone()).await?;
1386 view.delete_front().await?; save(&context, &mut view).await?;
1388
1389 let mut view = BucketQueueView::<_, u32, N>::load(context).await?;
1390 assert_eq!(view.elements().await?, vec![20, 30]);
1391
1392 view.clear();
1393 assert_eq!(view.elements().await?, Vec::<u32>::new());
1394
1395 view.rollback();
1396 assert_eq!(view.elements().await?, vec![20, 30]);
1397 assert!(!view.has_pending_changes().await);
1398 Ok(())
1399 }
1400
1401 async fn save<V: View>(context: &V::Context, view: &mut V) -> Result<(), ViewError> {
1402 let mut batch = Batch::new();
1403 view.pre_save(&mut batch)?;
1404 context.store().write_batch(batch).await?;
1405 view.post_save();
1406 Ok(())
1407 }
1408}