1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
45use fuchsia_inspect::Node;
6use std::collections::VecDeque;
78/// This struct is intended to represent a list node in Inspect, which doesn't support list
9/// natively. Furthermore, it makes sure that the number of items does not exceed |capacity|
10///
11/// Each item in `BoundedListNode` is represented as a child node with name as index. This
12/// index is always increasing and does not wrap around. For example, if capacity is 3,
13/// then the children names are `[0, 1, 2]` on first three addition. When a new node is
14/// added, `0` is popped, and the children names are `[1, 2, 3]`.
15#[derive(Debug)]
16pub struct BoundedListNode {
17 node: Node,
18 index: usize,
19 capacity: usize,
20 items: VecDeque<Node>,
21}
2223impl BoundedListNode {
24/// Create a new BoundedListNode with capacity 1 or |capacity|, whichever is larger.
25pub fn new(node: Node, capacity: usize) -> Self {
26Self {
27 node,
28 index: 0,
29 capacity: std::cmp::max(capacity, 1),
30 items: VecDeque::with_capacity(capacity),
31 }
32 }
3334/// Returns how many children are in the `BoundedListNode`.
35pub fn len(&self) -> usize {
36self.items.len()
37 }
3839/// Returns whether or not the bounded list has no elements inside.
40pub fn is_empty(&self) -> bool {
41self.items.len() == 0
42}
4344/// Returns the capacity of the `BoundedListNode`, the maximum number of child nodes.
45pub fn capacity(&self) -> usize {
46self.capacity
47 }
4849/// Create a new entry within a list and return a writer that creates properties or children
50 /// for this entry. The writer does not have to be kept for the created properties and
51 /// children to be maintained in the list.
52 ///
53 /// If creating new entry exceeds capacity of the list, the oldest entry is evicted.
54 ///
55 /// The `initialize` function will be used to atomically initialize all children and properties
56 /// under the node.
57pub fn add_entry<F>(&mut self, initialize: F) -> &Node
58where
59F: FnOnce(&Node),
60 {
61if self.items.len() >= self.capacity {
62self.items.pop_front();
63 }
6465let entry_node = self.node.atomic_update(|node| {
66let child = node.create_child(self.index.to_string());
67 initialize(&child);
68 child
69 });
70self.items.push_back(entry_node);
7172self.index += 1;
73self.items.back().unwrap()
74 }
75}
7677#[cfg(test)]
78mod tests {
79use super::*;
80use assert_matches::assert_matches;
81use diagnostics_assertions::assert_data_tree;
82use fuchsia_inspect::reader::{self, ReaderError};
83use fuchsia_inspect::Inspector;
84use std::sync::mpsc;
8586#[fuchsia::test]
87fn test_bounded_list_node_basic() {
88let inspector = Inspector::default();
89let list_node = inspector.root().create_child("list_node");
90let mut list_node = BoundedListNode::new(list_node, 3);
91assert_eq!(list_node.capacity(), 3);
92assert_eq!(list_node.len(), 0);
93let _ = list_node.add_entry(|_| {});
94assert_eq!(list_node.len(), 1);
95assert_data_tree!(inspector, root: { list_node: { "0": {} } });
96let _ = list_node.add_entry(|_| {});
97assert_eq!(list_node.len(), 2);
98assert_data_tree!(inspector, root: { list_node: { "0": {}, "1": {} } });
99 }
100101#[fuchsia::test]
102fn test_bounded_list_node_eviction() {
103let inspector = Inspector::default();
104let list_node = inspector.root().create_child("list_node");
105let mut list_node = BoundedListNode::new(list_node, 3);
106let _ = list_node.add_entry(|_| {});
107let _ = list_node.add_entry(|_| {});
108let _ = list_node.add_entry(|_| {});
109110assert_data_tree!(inspector, root: { list_node: { "0": {}, "1": {}, "2": {} } });
111assert_eq!(list_node.len(), 3);
112113let _ = list_node.add_entry(|_| {});
114assert_data_tree!(inspector, root: { list_node: { "1": {}, "2": {}, "3": {} } });
115assert_eq!(list_node.len(), 3);
116117let _ = list_node.add_entry(|_| {});
118assert_data_tree!(inspector, root: { list_node: { "2": {}, "3": {}, "4": {} } });
119assert_eq!(list_node.len(), 3);
120 }
121122#[fuchsia::test]
123fn test_bounded_list_node_specified_zero_capacity() {
124let inspector = Inspector::default();
125let list_node = inspector.root().create_child("list_node");
126let mut list_node = BoundedListNode::new(list_node, 0);
127let _ = list_node.add_entry(|_| {});
128assert_data_tree!(inspector, root: { list_node: { "0": {} } });
129let _ = list_node.add_entry(|_| {});
130assert_data_tree!(inspector, root: { list_node: { "1": {} } });
131 }
132133#[fuchsia::test]
134fn test_bounded_list_node_holds_its_values() {
135let inspector = Inspector::default();
136let list_node = inspector.root().create_child("list_node");
137let mut list_node = BoundedListNode::new(list_node, 3);
138139 {
140let node_writer = list_node.add_entry(|_| {});
141 node_writer.record_string("str_key", "str_value");
142 node_writer.record_child("child", |child| child.record_int("int_key", 2));
143 } // <-- node_writer is dropped
144145 // verify list node 0 is still in the tree
146assert_data_tree!(inspector, root: {
147 list_node: {
148"0": {
149 str_key: "str_value",
150 child: {
151 int_key: 2i64,
152 }
153 }
154 }
155 });
156 }
157158#[fuchsia::test]
159async fn add_entry_is_atomic() {
160let inspector = Inspector::default();
161let list_node = inspector.root().create_child("list_node");
162let mut list_node = BoundedListNode::new(list_node, 3);
163164let (sender, receiver) = mpsc::channel();
165let (sender2, receiver2) = mpsc::channel();
166167let t = std::thread::spawn(move || {
168 list_node.add_entry(|node| {
169 node.record_string("key1", "value1");
170 sender.send(()).unwrap();
171 receiver2.recv().unwrap();
172 node.record_string("key2", "value2");
173 });
174 list_node
175 });
176177// Make sure we already called `add_entry`.
178receiver.recv().unwrap();
179180// We can't read until the atomic transaction is completed.
181assert_matches!(reader::read(&inspector).await, Err(ReaderError::InconsistentSnapshot));
182183// Let `add_entry` continue executing and wait for completion.
184sender2.send(()).unwrap();
185186// Ensure we don't drop the list node.
187let _list_node = t.join().unwrap();
188189// We can now read and we can see that everything was correctly created.
190assert_data_tree!(inspector, root: {
191 list_node: {
192"0": {
193 key1: "value1",
194 key2: "value2",
195 }
196 }
197 });
198 }
199}