1use memory_mapped_vmo::{
6 ReadOnlyMemoryMappedVmo, ReadWriteMemoryMappedArcVmo, ReadWriteMemoryMappedVmo,
7};
8use static_assertions::const_assert;
9use std::collections::HashSet;
10use std::mem::{align_of, size_of};
11use std::ops::Deref;
12use std::sync::Mutex;
13use std::sync::atomic::AtomicU32;
14use std::sync::atomic::Ordering::{Relaxed, SeqCst};
15use zerocopy::FromBytes;
16
17pub use crate::resources_table_v1::ResourceKey;
18
19type NodeIndex = u32;
20type BucketHeads = [AtomicNodeIndex; NUM_BUCKETS];
21const NUM_BUCKETS: usize = 1 << 16;
22const NODE_INVALID: NodeIndex = NodeIndex::MAX;
23
24pub const MIN_ALIGNMENT: usize = align_of::<Node>();
26const_assert!(MIN_ALIGNMENT % align_of::<BucketHeads>() == 0);
27
28const LOCAL_CACHE_CAPACITY: usize = 24;
31
32const REFILL_SPILL_BATCH: usize = 12;
34
35#[repr(transparent)]
37#[derive(Debug, FromBytes)]
38struct AtomicNodeIndex(AtomicU32);
39
40impl AtomicNodeIndex {
41 const fn new(value: u32) -> AtomicNodeIndex {
42 AtomicNodeIndex(AtomicU32::new(value))
43 }
44}
45
46impl Deref for AtomicNodeIndex {
47 type Target = AtomicU32;
48
49 fn deref(&self) -> &AtomicU32 {
50 &self.0
51 }
52}
53
54#[repr(C)]
55#[derive(Debug, FromBytes)]
56pub struct Node {
57 next: AtomicNodeIndex,
58 pub address: u64,
59 pub size: u64,
60 pub timestamp: zx::MonotonicInstant,
61 pub thread_info_key: ResourceKey,
62 pub stack_trace_key: ResourceKey,
63}
64
65fn compute_nodes_count(num_bytes: usize) -> Result<usize, crate::Error> {
67 let Some(nodes_size) = num_bytes.checked_sub(size_of::<BucketHeads>()) else {
68 return Err(crate::Error::BufferTooSmall);
69 };
70
71 let num_nodes = nodes_size / size_of::<Node>();
72 if num_nodes > NODE_INVALID as usize {
73 return Err(crate::Error::BufferTooBig);
74 }
75
76 Ok(num_nodes)
77}
78
79#[derive(Clone, Copy, Debug, PartialEq, Eq)]
81pub struct AllocationKey<const NUM_SHARDS: usize> {
82 shard_index: usize,
83 bucket_index: usize,
84 address: u64,
85}
86
87impl<const NUM_SHARDS: usize> AllocationKey<NUM_SHARDS> {
88 pub const fn make(address: u64) -> AllocationKey<NUM_SHARDS> {
90 let key = address ^ (address >> 33);
91 let hash = key.wrapping_mul(0x9e3779b97f4a7c15);
92
93 const { assert!(NUM_BUCKETS.is_power_of_two()) };
95 const { assert!(NUM_SHARDS.is_power_of_two()) };
96 const { assert!(NUM_BUCKETS >= NUM_SHARDS) };
97 let bucket_index = (hash >> (64 - NUM_BUCKETS.ilog2())) as usize;
98 let shard_index = bucket_index / (NUM_BUCKETS / NUM_SHARDS);
99
100 AllocationKey { shard_index, bucket_index, address }
101 }
102
103 pub const fn shard_index(&self) -> usize {
104 self.shard_index
105 }
106
107 pub const fn address(&self) -> u64 {
108 self.address
109 }
110}
111
112pub struct AllocationsTableWriter<const NUM_SHARDS: usize> {
128 storage: ReadWriteMemoryMappedArcVmo<Mutex<AllocationsTableWriterSharedState>>,
129 shard_index: usize,
130 local_free_list_head: NodeIndex,
131 local_free_count: usize,
132}
133
134struct AllocationsTableWriterSharedState {
143 watermark: NodeIndex,
146 max_num_nodes: usize,
147
148 free_list_head: NodeIndex,
152}
153
154impl<const NUM_SHARDS: usize> AllocationsTableWriter<NUM_SHARDS> {
155 pub unsafe fn new(
165 vmo: &zx::Vmo,
166 control_vmo_name: &zx::Name,
167 ) -> Result<[AllocationsTableWriter<NUM_SHARDS>; NUM_SHARDS], crate::Error> {
168 let mut raw_storage = unsafe { ReadWriteMemoryMappedVmo::new(vmo)? };
169 let max_num_nodes = compute_nodes_count(raw_storage.vmo_size())?;
170
171 let bucket_heads = raw_storage.get_object_mut::<BucketHeads>(0).unwrap();
173 for bucket_head in bucket_heads.iter() {
174 bucket_head.store(NODE_INVALID, SeqCst);
175 }
176
177 let shared_allocator = Mutex::new(AllocationsTableWriterSharedState {
178 max_num_nodes,
179 watermark: 0,
180 free_list_head: NODE_INVALID,
181 });
182 let storage = unsafe {
183 ReadWriteMemoryMappedArcVmo::wrap_with_name(
184 raw_storage,
185 shared_allocator,
186 control_vmo_name,
187 )?
188 };
189
190 Ok(std::array::from_fn(|shard_index| AllocationsTableWriter {
191 storage: storage.clone(),
192 shard_index,
193 local_free_list_head: NODE_INVALID,
194 local_free_count: 0,
195 }))
196 }
197
198 fn bucket_head_at(&self, bucket_index: usize) -> &AtomicNodeIndex {
200 let bucket_heads = self.storage.get_object::<BucketHeads>(0).unwrap();
202 &bucket_heads[bucket_index]
203 }
204
205 fn node_at(&self, node_index: NodeIndex) -> &Node {
207 let byte_offset = size_of::<BucketHeads>() + node_index as usize * size_of::<Node>();
209 self.storage.get_object::<Node>(byte_offset).unwrap()
210 }
211
212 fn node_at_mut(&mut self, node_index: NodeIndex) -> &mut Node {
214 let byte_offset = size_of::<BucketHeads>() + node_index as usize * size_of::<Node>();
216 self.storage.get_object_mut::<Node>(byte_offset).unwrap()
217 }
218
219 pub fn insert_allocation(
224 &mut self,
225 key: AllocationKey<NUM_SHARDS>,
226 size: u64,
227 thread_info_key: ResourceKey,
228 stack_trace_key: ResourceKey,
229 timestamp: zx::MonotonicInstant,
230 ) -> Result<bool, crate::Error> {
231 assert_eq!(key.shard_index, self.shard_index);
232
233 let old_head = self.bucket_head_at(key.bucket_index).load(Relaxed);
234
235 let mut curr_index = old_head;
237 while curr_index != NODE_INVALID {
238 let curr_data = self.node_at(curr_index);
239 if curr_data.address == key.address {
240 return Ok(false);
241 }
242 curr_index = curr_data.next.load(Relaxed);
243 }
244
245 let new_index = self.pop_free_node()?;
247 *self.node_at_mut(new_index) = Node {
248 address: key.address,
249 size,
250 timestamp,
251 thread_info_key,
252 stack_trace_key,
253 next: AtomicNodeIndex::new(old_head),
254 };
255 self.bucket_head_at(key.bucket_index).store(new_index, SeqCst);
256 Ok(true)
257 }
258
259 pub fn erase_allocation(&mut self, key: AllocationKey<NUM_SHARDS>) -> Option<u64> {
262 assert_eq!(key.shard_index, self.shard_index);
263
264 let mut prev_index = None;
266 let mut curr_index = self.bucket_head_at(key.bucket_index).load(Relaxed);
267 while curr_index != NODE_INVALID {
268 let curr_data = self.node_at(curr_index);
269 let curr_data_size = curr_data.size;
270 let curr_data_next = curr_data.next.load(Relaxed);
271
272 if curr_data.address == key.address {
274 if let Some(prev_index) = prev_index {
275 self.node_at(prev_index).next.store(curr_data_next, SeqCst);
276 } else {
277 self.bucket_head_at(key.bucket_index).store(curr_data_next, SeqCst);
278 }
279 self.push_free_node(curr_index);
280 return Some(curr_data_size);
281 }
282
283 prev_index = Some(curr_index);
284 curr_index = curr_data.next.load(Relaxed);
285 }
286
287 None
289 }
290
291 pub fn replace_allocation(
296 &mut self,
297 key: AllocationKey<NUM_SHARDS>,
298 size: u64,
299 thread_info_key: ResourceKey,
300 stack_trace_key: ResourceKey,
301 timestamp: zx::MonotonicInstant,
302 ) -> Result<Option<u64>, crate::Error> {
303 assert_eq!(key.shard_index, self.shard_index);
304
305 let Some((new_node, old_node, old_size)) =
314 self.replace_allocation_begin(key, size, thread_info_key, stack_trace_key, timestamp)?
315 else {
316 return Ok(None);
317 };
318
319 self.replace_allocation_end(new_node, old_node);
321 Ok(Some(old_size))
322 }
323
324 fn replace_allocation_begin(
326 &mut self,
327 key: AllocationKey<NUM_SHARDS>,
328 size: u64,
329 thread_info_key: ResourceKey,
330 stack_trace_key: ResourceKey,
331 timestamp: zx::MonotonicInstant,
332 ) -> Result<Option<(NodeIndex, NodeIndex, u64)>, crate::Error> {
333 let old_head = self.bucket_head_at(key.bucket_index).load(Relaxed);
334
335 let (old_index, old_size) = 'search: {
337 let mut curr_index = old_head;
338 while curr_index != NODE_INVALID {
339 let curr_data = self.node_at(curr_index);
340 if curr_data.address == key.address {
341 break 'search (curr_index, curr_data.size); }
343 curr_index = curr_data.next.load(Relaxed);
344 }
345
346 return Ok(None); };
348
349 let new_index = self.pop_free_node()?;
351 *self.node_at_mut(new_index) = Node {
352 address: key.address,
353 size,
354 timestamp,
355 thread_info_key,
356 stack_trace_key,
357 next: AtomicNodeIndex::new(old_head),
358 };
359 self.bucket_head_at(key.bucket_index).store(new_index, SeqCst);
360 Ok(Some((new_index, old_index, old_size)))
361 }
362
363 fn replace_allocation_end(&mut self, new_node: NodeIndex, old_node: NodeIndex) {
365 let tail = self.node_at(old_node).next.load(Relaxed);
366
367 let mut scan_node = new_node;
370 loop {
371 assert_ne!(scan_node, NODE_INVALID, "the old node must be a successor of new node");
372 let scan_data = self.node_at(scan_node);
373 if scan_data.next.load(Relaxed) == old_node {
374 scan_data.next.store(tail, SeqCst);
375 self.push_free_node(old_node);
376 return;
377 }
378 scan_node = scan_data.next.load(Relaxed);
379 }
380 }
381
382 fn push_free_node(&mut self, index: NodeIndex) {
385 self.node_at(index).next.store(self.local_free_list_head, Relaxed);
386 self.local_free_list_head = index;
387 self.local_free_count += 1;
388
389 if self.local_free_count > LOCAL_CACHE_CAPACITY {
390 let mut shared = self.storage.metadata().lock().unwrap();
391
392 for _ in 0..REFILL_SPILL_BATCH {
394 let node_index = self.local_free_list_head;
395 let node_data = self.node_at(node_index);
396 let next_local = node_data.next.load(Relaxed);
397
398 node_data.next.store(shared.free_list_head, Relaxed);
399 shared.free_list_head = node_index;
400
401 self.local_free_list_head = next_local;
402 self.local_free_count -= 1;
403 }
404 }
405 }
406
407 fn pop_free_node(&mut self) -> Result<NodeIndex, crate::Error> {
410 if self.local_free_list_head == NODE_INVALID {
411 let mut shared = self.storage.metadata().lock().unwrap();
412
413 while self.local_free_count < REFILL_SPILL_BATCH {
415 let new_node = if shared.free_list_head != NODE_INVALID {
416 let index = shared.free_list_head;
418 shared.free_list_head = self.node_at(index).next.load(Relaxed);
419 index
420 } else if (shared.watermark as usize) < shared.max_num_nodes {
421 let index = shared.watermark;
423 shared.watermark += 1;
424 index
425 } else {
426 break;
427 };
428
429 self.node_at(new_node).next.store(self.local_free_list_head, Relaxed);
430 self.local_free_list_head = new_node;
431 self.local_free_count += 1;
432 }
433 }
434
435 if self.local_free_list_head != NODE_INVALID {
436 let result = self.local_free_list_head;
437 self.local_free_list_head = self.node_at(result).next.load(Relaxed);
438 self.local_free_count -= 1;
439 Ok(result)
440 } else {
441 Err(crate::Error::OutOfSpace)
443 }
444 }
445}
446
447pub struct AllocationsTableReader {
449 storage: ReadOnlyMemoryMappedVmo,
450 max_num_nodes: usize,
451}
452
453impl AllocationsTableReader {
454 pub unsafe fn new(vmo: &zx::Vmo) -> Result<AllocationsTableReader, crate::Error> {
459 let storage = unsafe { ReadOnlyMemoryMappedVmo::new(vmo)? };
460 let max_num_nodes = compute_nodes_count(storage.vmo_size())?;
461
462 Ok(AllocationsTableReader { storage, max_num_nodes })
463 }
464
465 pub fn iter(&self) -> impl Iterator<Item = Result<&Node, crate::Error>> {
467 let bucket_heads = self.storage.get_object::<BucketHeads>(0).unwrap();
469 bucket_heads.iter().map(|head| self.iterate_bucket(head.load(Relaxed))).flatten()
470 }
471
472 fn iterate_bucket(&self, head: NodeIndex) -> impl Iterator<Item = Result<&Node, crate::Error>> {
473 let mut curr_index = head;
474 let mut seen_addresses = HashSet::new();
475 std::iter::from_fn(move || {
476 while curr_index != NODE_INVALID {
477 if (curr_index as usize) < self.max_num_nodes {
478 let byte_offset =
480 size_of::<BucketHeads>() + curr_index as usize * size_of::<Node>();
481 let curr_data = self.storage.get_object::<Node>(byte_offset).unwrap();
482 curr_index = curr_data.next.load(Relaxed);
483
484 if seen_addresses.insert(curr_data.address) {
487 return Some(Ok(curr_data));
488 }
489 } else {
490 return Some(Err(crate::Error::InvalidInput));
491 };
492 }
493
494 None
495 })
496 }
497}
498
499#[cfg(test)]
500mod tests {
501 use super::*;
502 use assert_matches::assert_matches;
503 use std::alloc::Layout;
504 use std::collections::HashMap;
505
506 const NUM_ITERATIONS: usize = NUM_BUCKETS * 10;
508
509 const NUM_NODES: usize = NUM_ITERATIONS + 100;
511
512 const THREAD_INFO_RESOURCE_KEY_1: ResourceKey = ResourceKey::from_raw(0x1122);
514 const THREAD_INFO_RESOURCE_KEY_2: ResourceKey = ResourceKey::from_raw(0x3344);
515 const STACK_TRACE_RESOURCE_KEY_1: ResourceKey = ResourceKey::from_raw(0x1212);
516 const STACK_TRACE_RESOURCE_KEY_2: ResourceKey = ResourceKey::from_raw(0x3434);
517
518 struct TestStorage {
519 vmo: zx::Vmo,
520 }
521
522 impl TestStorage {
523 pub fn new(num_nodes: usize) -> (TestStorage, AllocationsTableWriter<1>) {
524 let (storage, [writer]) = Self::new_sharded::<1>(num_nodes);
525 (storage, writer)
526 }
527
528 pub fn new_sharded<const NUM_SHARDS: usize>(
529 num_nodes: usize,
530 ) -> (TestStorage, [AllocationsTableWriter<NUM_SHARDS>; NUM_SHARDS]) {
531 let nodes_layout = Layout::array::<Node>(num_nodes).unwrap();
532 let (layout, nodes_offset) = Layout::new::<BucketHeads>().extend(nodes_layout).unwrap();
533 assert_eq!(nodes_offset, size_of::<BucketHeads>());
534
535 let vmo = zx::Vmo::create(layout.size() as u64).unwrap();
536
537 let writers =
539 unsafe { AllocationsTableWriter::new(&vmo, &zx::Name::default()) }.unwrap();
540
541 (TestStorage { vmo }, writers)
542 }
543
544 fn create_reader(&self) -> AllocationsTableReader {
545 let snapshot = self
546 .vmo
547 .create_child(
548 zx::VmoChildOptions::SNAPSHOT | zx::VmoChildOptions::NO_WRITE,
549 0,
550 self.vmo.get_size().unwrap(),
551 )
552 .unwrap();
553
554 unsafe { AllocationsTableReader::new(&snapshot) }.unwrap()
556 }
557 }
558
559 #[test]
560 fn test_cannot_insert_twice() {
561 let (_storage, mut writer) = TestStorage::new(NUM_NODES);
562
563 let result = writer.insert_allocation(
564 AllocationKey::make(0x1234),
565 0x5678,
566 THREAD_INFO_RESOURCE_KEY_1,
567 STACK_TRACE_RESOURCE_KEY_1,
568 zx::MonotonicInstant::ZERO,
569 );
570 assert_eq!(result, Ok(true));
571
572 let result = writer.insert_allocation(
573 AllocationKey::make(0x1234),
574 0x5678,
575 THREAD_INFO_RESOURCE_KEY_1,
576 STACK_TRACE_RESOURCE_KEY_1,
577 zx::MonotonicInstant::ZERO,
578 );
579 assert_eq!(result, Ok(false));
580 }
581
582 #[test]
583 fn test_cannot_erase_twice() {
584 let (_storage, mut writer) = TestStorage::new(NUM_NODES);
585
586 let result = writer.insert_allocation(
587 AllocationKey::make(0x1234),
588 0x5678,
589 THREAD_INFO_RESOURCE_KEY_1,
590 STACK_TRACE_RESOURCE_KEY_1,
591 zx::MonotonicInstant::ZERO,
592 );
593 assert_eq!(result, Ok(true));
594
595 let result = writer.erase_allocation(AllocationKey::make(0x1234));
596 assert_eq!(result, Some(0x5678));
597
598 let result = writer.erase_allocation(AllocationKey::make(0x1234));
599 assert_eq!(result, None);
600 }
601
602 #[test]
603 fn test_out_of_space() {
604 let (_storage, mut writer) = TestStorage::new(NUM_NODES);
605
606 for i in 0..NUM_NODES {
608 let result = writer.insert_allocation(
609 AllocationKey::make(i as u64),
610 1,
611 THREAD_INFO_RESOURCE_KEY_1,
612 STACK_TRACE_RESOURCE_KEY_1,
613 zx::MonotonicInstant::ZERO,
614 );
615 assert_eq!(result, Ok(true));
616 }
617
618 let result = writer.insert_allocation(
620 AllocationKey::make(NUM_NODES as u64),
621 1,
622 THREAD_INFO_RESOURCE_KEY_1,
623 STACK_TRACE_RESOURCE_KEY_1,
624 zx::MonotonicInstant::ZERO,
625 );
626 assert_eq!(result, Err(crate::Error::OutOfSpace));
627
628 let result = writer.erase_allocation(AllocationKey::make(0));
630 assert_eq!(result, Some(1));
631 let result = writer.insert_allocation(
632 AllocationKey::make(NUM_NODES as u64),
633 1,
634 THREAD_INFO_RESOURCE_KEY_1,
635 STACK_TRACE_RESOURCE_KEY_1,
636 zx::MonotonicInstant::ZERO,
637 );
638 assert_eq!(result, Ok(true));
639 }
640
641 #[test]
642 fn test_loop_insert_then_erase() {
643 let (_storage, mut writer) = TestStorage::new(NUM_NODES);
644
645 for i in 0..NUM_ITERATIONS {
646 let result = writer.insert_allocation(
647 AllocationKey::make(i as u64),
648 1,
649 THREAD_INFO_RESOURCE_KEY_1,
650 STACK_TRACE_RESOURCE_KEY_1,
651 zx::MonotonicInstant::ZERO,
652 );
653 assert_eq!(result, Ok(true), "failed to insert 0x{:x}", i);
654
655 let result = writer.erase_allocation(AllocationKey::make(i as u64));
656 assert_eq!(result, Some(1), "failed to erase 0x{:x}", i);
657 }
658 }
659
660 #[test]
661 fn test_bulk_insert_then_erase_same_order() {
662 let (_storage, mut writer) = TestStorage::new(NUM_NODES);
663
664 for i in 0..NUM_ITERATIONS {
665 let result = writer.insert_allocation(
666 AllocationKey::make(i as u64),
667 1,
668 THREAD_INFO_RESOURCE_KEY_1,
669 STACK_TRACE_RESOURCE_KEY_1,
670 zx::MonotonicInstant::ZERO,
671 );
672 assert_eq!(result, Ok(true), "failed to insert 0x{:x}", i);
673 }
674 for i in 0..NUM_ITERATIONS {
675 let result = writer.erase_allocation(AllocationKey::make(i as u64));
676 assert_eq!(result, Some(1), "failed to erase 0x{:x}", i);
677 }
678 }
679
680 #[test]
681 fn test_bulk_insert_then_erase_reverse_order() {
682 let (_storage, mut writer) = TestStorage::new(NUM_NODES);
683
684 for i in 0..NUM_ITERATIONS {
685 let result = writer.insert_allocation(
686 AllocationKey::make(i as u64),
687 1,
688 THREAD_INFO_RESOURCE_KEY_1,
689 STACK_TRACE_RESOURCE_KEY_1,
690 zx::MonotonicInstant::ZERO,
691 );
692 assert_eq!(result, Ok(true), "failed to insert 0x{:x}", i);
693 }
694 for i in (0..NUM_ITERATIONS).rev() {
695 let result = writer.erase_allocation(AllocationKey::make(i as u64));
696 assert_eq!(result, Some(1), "failed to erase 0x{:x}", i);
697 }
698 }
699
700 #[test]
701 fn test_read_empty() {
702 let (storage, _writer) = TestStorage::new(NUM_NODES);
703
704 let reader = storage.create_reader();
706 assert_eq!(reader.iter().count(), 0);
707 }
708
709 #[test]
710 fn test_read_populated() {
711 let (storage, mut writer) = TestStorage::new(NUM_NODES);
712
713 let mut expected_map = HashMap::new();
715 for i in 0..NUM_ITERATIONS as u64 {
716 let thread_info_key =
717 if i % 4 >= 2 { THREAD_INFO_RESOURCE_KEY_1 } else { THREAD_INFO_RESOURCE_KEY_2 };
718 let stack_trace_key =
719 if i % 2 == 0 { STACK_TRACE_RESOURCE_KEY_1 } else { STACK_TRACE_RESOURCE_KEY_2 };
720 let timestamp =
721 zx::MonotonicInstant::from_nanos((NUM_ITERATIONS as i64 / 2) - (i as i64)); let result = writer.insert_allocation(
723 AllocationKey::make(i),
724 1,
725 thread_info_key,
726 stack_trace_key,
727 timestamp,
728 );
729 assert_eq!(result, Ok(true), "failed to insert 0x{:x}", i);
730
731 expected_map.insert(i, (1, thread_info_key, stack_trace_key, timestamp));
732 }
733
734 let reader = storage.create_reader();
736 let mut actual_map = HashMap::new();
737 for node in reader.iter() {
738 let Node { address, size, thread_info_key, stack_trace_key, timestamp, .. } =
739 node.unwrap();
740 assert!(
741 actual_map
742 .insert(*address, (*size, *thread_info_key, *stack_trace_key, *timestamp))
743 .is_none(),
744 "address 0x{:x} was read more than once",
745 address
746 );
747 }
748
749 assert_eq!(actual_map, expected_map);
750 }
751
752 #[test]
753 fn test_read_bad_bucket_head() {
754 let (storage, writer) = TestStorage::new(NUM_NODES);
755
756 writer.bucket_head_at(NUM_BUCKETS / 2).store(NODE_INVALID - 1, SeqCst);
758
759 let reader = storage.create_reader();
761 let contains_error = reader.iter().any(|e| e.is_err());
762
763 assert!(contains_error);
764 }
765
766 #[test]
767 fn test_read_bad_node_next() {
768 let (storage, mut writer) = TestStorage::new(NUM_NODES);
769
770 writer.bucket_head_at(NUM_BUCKETS / 2).store(0, SeqCst);
772 *writer.node_at_mut(0) = Node {
773 next: AtomicNodeIndex::new(NODE_INVALID - 1),
774 address: 0x1234,
775 size: 0x5678,
776 thread_info_key: THREAD_INFO_RESOURCE_KEY_1,
777 stack_trace_key: STACK_TRACE_RESOURCE_KEY_1,
778 timestamp: zx::MonotonicInstant::from_nanos(99999999),
779 };
780
781 let reader = storage.create_reader();
783 let contains_error = reader.iter().any(|e| e.is_err());
784
785 assert!(contains_error);
786 }
787
788 #[test]
789 fn test_replace() {
790 let (storage, mut writer) = TestStorage::new(NUM_NODES);
791
792 let result = writer.insert_allocation(
793 AllocationKey::make(0x1234),
794 0x1111,
795 THREAD_INFO_RESOURCE_KEY_1,
796 STACK_TRACE_RESOURCE_KEY_1,
797 zx::MonotonicInstant::ZERO,
798 );
799 assert_eq!(result, Ok(true));
800
801 let reader = storage.create_reader();
803 let mut iter = reader.iter();
804 assert_matches!(iter.next(), Some(Ok(Node { address: 0x1234, size: 0x1111, .. })));
805 assert_matches!(iter.next(), None);
806
807 let result = writer.replace_allocation_begin(
808 AllocationKey::make(0x1234),
809 0x2222,
810 THREAD_INFO_RESOURCE_KEY_2,
811 STACK_TRACE_RESOURCE_KEY_2,
812 zx::MonotonicInstant::ZERO,
813 );
814 let Ok(Some((new_node, old_node, old_size))) = result else {
815 panic!("Update begin is supposed to succeed in this test, got {:?} instead", result)
816 };
817 assert_eq!(old_size, 0x1111);
818
819 let reader = storage.create_reader();
821 let mut iter = reader.iter();
822 assert_matches!(iter.next(), Some(Ok(Node { address: 0x1234, size: 0x2222, .. })));
823 assert_matches!(iter.next(), None);
824
825 writer.replace_allocation_end(new_node, old_node);
826
827 let reader = storage.create_reader();
829 let mut iter = reader.iter();
830 assert_matches!(iter.next(), Some(Ok(Node { address: 0x1234, size: 0x2222, .. })));
831 assert_matches!(iter.next(), None);
832 }
833
834 #[test]
835 fn test_cannot_replace_nonexisting() {
836 let (_storage, mut writer) = TestStorage::new(NUM_NODES);
837
838 let result = writer.replace_allocation(
839 AllocationKey::make(0x1234),
840 0x5678,
841 THREAD_INFO_RESOURCE_KEY_1,
842 STACK_TRACE_RESOURCE_KEY_1,
843 zx::MonotonicInstant::ZERO,
844 );
845 assert_eq!(result, Ok(None));
846 }
847
848 #[test]
849 fn test_sharded_spill_and_refill() {
850 let (storage, [mut writer0, mut writer1]) = TestStorage::new_sharded::<2>(NUM_NODES);
851
852 let key_for_shard = |shard: usize, i: usize| -> AllocationKey<2> {
854 AllocationKey {
855 shard_index: shard,
856 bucket_index: (shard * NUM_BUCKETS + i % NUM_BUCKETS) / 2,
857 address: ((i * 2 + shard) as u64) << 4,
858 }
859 };
860
861 for i in 0..NUM_NODES {
863 assert_eq!(
864 writer0.insert_allocation(
865 key_for_shard(0, i),
866 1,
867 THREAD_INFO_RESOURCE_KEY_1,
868 STACK_TRACE_RESOURCE_KEY_1,
869 zx::MonotonicInstant::ZERO,
870 ),
871 Ok(true)
872 );
873 }
874
875 assert_eq!(
877 writer1.insert_allocation(
878 key_for_shard(1, 0),
879 1,
880 THREAD_INFO_RESOURCE_KEY_1,
881 STACK_TRACE_RESOURCE_KEY_1,
882 zx::MonotonicInstant::ZERO,
883 ),
884 Err(crate::Error::OutOfSpace)
885 );
886
887 for i in 0..(LOCAL_CACHE_CAPACITY + 1) {
890 assert_eq!(writer0.erase_allocation(key_for_shard(0, i)), Some(1));
891 }
892
893 for i in 0..REFILL_SPILL_BATCH {
895 assert_eq!(
896 writer1.insert_allocation(
897 key_for_shard(1, i),
898 2,
899 THREAD_INFO_RESOURCE_KEY_2,
900 STACK_TRACE_RESOURCE_KEY_2,
901 zx::MonotonicInstant::ZERO,
902 ),
903 Ok(true)
904 );
905 }
906
907 let reader = storage.create_reader();
908 assert_eq!(
909 reader.iter().count(),
910 NUM_NODES - (LOCAL_CACHE_CAPACITY + 1) + REFILL_SPILL_BATCH
911 );
912 }
913}