1use super::object_record::{ObjectKey, ObjectKeyData, ObjectValue};
6use crate::lsm_tree::cache::{ObjectCache, ObjectCachePlaceholder, ObjectCacheResult};
7use fuchsia_sync::Mutex;
8use linked_hash_map::{Entry, LinkedHashMap};
9use std::hash::BuildHasherDefault;
10use std::sync::atomic::{AtomicU64, Ordering};
11
12fn filter(key: &ObjectKey) -> bool {
13 match key.data {
14 ObjectKeyData::Object => true,
17 ObjectKeyData::ExtendedAttribute { .. } => true,
18 _ => false,
19 }
20}
21
22const ITEM_LIMIT: usize = 1535;
26
27struct Placeholder<'a> {
28 cache: &'a TreeCache,
29 key: ObjectKey,
30 placeholder_id: u64,
31}
32
33impl Placeholder<'_> {
34 fn replace_entry(&mut self, value: Option<CacheValue>) {
35 let key = std::mem::replace(&mut self.key, ObjectKey::object(0));
36 let mut inner = self.cache.inner.lock();
37 if let Entry::Occupied(mut entry) = inner.entry(key) {
39 let is_current = match entry.get() {
41 CacheValue::Placeholder(placeholder_id) => &self.placeholder_id == placeholder_id,
42 _ => false,
43 };
44 if is_current {
45 match value {
46 Some(v) => *(entry.get_mut()) = v,
47 None => {
48 entry.remove();
49 }
50 }
51 }
52 }
53 }
54}
55
56impl Drop for Placeholder<'_> {
57 fn drop(&mut self) {
58 self.replace_entry(None);
59 }
60}
61
62impl<'a> ObjectCachePlaceholder<ObjectValue> for Placeholder<'a> {
63 fn complete(mut self: Box<Self>, value: Option<&ObjectValue>) {
64 let entry_value = match value {
65 value @ Some(ObjectValue::Object { .. }) => value.cloned(),
66 value @ Some(ObjectValue::ExtendedAttribute(_)) => value.cloned(),
67 _ => None,
68 }
69 .map(|v| CacheValue::Value(v));
70 self.replace_entry(entry_value);
71 }
72}
73
74enum CacheValue {
75 Placeholder(u64),
76 Value(ObjectValue),
77}
78
79pub struct TreeCache {
81 inner: Mutex<LinkedHashMap<ObjectKey, CacheValue, BuildHasherDefault<rustc_hash::FxHasher>>>,
82 placeholder_counter: AtomicU64,
83}
84
85impl TreeCache {
86 pub fn new() -> Self {
87 Self {
88 inner: Mutex::new(LinkedHashMap::with_capacity_and_hasher(
89 ITEM_LIMIT + 1,
90 BuildHasherDefault::<rustc_hash::FxHasher>::default(),
91 )),
92 placeholder_counter: AtomicU64::new(0),
93 }
94 }
95}
96
97impl ObjectCache<ObjectKey, ObjectValue> for TreeCache {
98 fn lookup_or_reserve(&self, key: &ObjectKey) -> ObjectCacheResult<'_, ObjectValue> {
99 if !filter(key) {
100 return ObjectCacheResult::NoCache;
101 }
102 let mut inner = self.inner.lock();
103 match inner.get_refresh(key) {
104 Some(CacheValue::Value(entry)) => ObjectCacheResult::Value(entry.clone()),
105 Some(CacheValue::Placeholder(_)) => ObjectCacheResult::NoCache,
106 _ => {
107 let placeholder_id = self.placeholder_counter.fetch_add(1, Ordering::Relaxed);
108 inner.insert(key.clone(), CacheValue::Placeholder(placeholder_id));
109 if inner.len() > ITEM_LIMIT {
110 let _ = inner.pop_front();
111 }
112 ObjectCacheResult::Placeholder(Box::new(Placeholder {
113 cache: self,
114 key: key.clone(),
115 placeholder_id,
116 }))
117 }
118 }
119 }
120
121 fn invalidate(&self, key: ObjectKey, value: Option<ObjectValue>) {
122 if !filter(&key) {
123 return;
124 }
125 let mut inner = self.inner.lock();
126 if let Entry::Occupied(mut entry) = inner.entry(key) {
127 if let Some(replacement) = value {
128 *(entry.get_mut()) = CacheValue::Value(replacement);
129 } else {
130 entry.remove();
131 }
132 }
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::super::object_record::{ObjectKey, ObjectValue, Timestamp};
139 use super::{TreeCache, ITEM_LIMIT};
140 use crate::lsm_tree::cache::{ObjectCache, ObjectCacheResult};
141 use assert_matches::assert_matches;
142
143 #[fuchsia::test]
144 async fn test_basic_operations() {
145 let cache = TreeCache::new();
146 let key = ObjectKey::object(1);
147 let now = Timestamp::now();
148 let value = ObjectValue::file(1, 0, now, now, now, now, 0, None);
149
150 let placeholder = match cache.lookup_or_reserve(&key) {
151 ObjectCacheResult::Placeholder(placeholder) => placeholder,
152 _ => panic!("Expected cache miss with placeholder returned."),
153 };
154 placeholder.complete(Some(&value));
155
156 let result = match cache.lookup_or_reserve(&key) {
157 ObjectCacheResult::Value(value) => value,
158 _ => panic!("Expected to find item."),
159 };
160 assert_eq!(&result, &value);
161
162 cache.invalidate(key.clone(), None);
163
164 match cache.lookup_or_reserve(&key) {
165 ObjectCacheResult::Placeholder(placeholder) => placeholder.complete(None),
166 _ => panic!("Expected cache miss with placeholder returned."),
167 };
168 }
169
170 #[fuchsia::test]
171 async fn test_enforce_limits() {
172 let cache = TreeCache::new();
173 let now = Timestamp::now();
174
175 for i in 1..(ITEM_LIMIT as u64 + 2) {
176 let key = ObjectKey::object(i);
177 let value = ObjectValue::file(1, 0, now, now, now, now, 0, None);
178 let placeholder = match cache.lookup_or_reserve(&key) {
179 ObjectCacheResult::Placeholder(placeholder) => placeholder,
180 _ => panic!("Expected cache miss with placeholder returned."),
181 };
182 placeholder.complete(Some(&value));
183 }
184
185 assert_matches!(
187 cache.lookup_or_reserve(&ObjectKey::object(1)),
188 ObjectCacheResult::Placeholder(_)
189 );
190
191 for i in 3..(ITEM_LIMIT as u64 + 2) {
193 let key = ObjectKey::object(i);
194 assert_matches!(cache.lookup_or_reserve(&key), ObjectCacheResult::Value(_));
195 }
196 }
197
198 #[fuchsia::test]
199 async fn test_invalidate_inserts() {
200 let cache = TreeCache::new();
201 let key = ObjectKey::object(1);
202 let now = Timestamp::now();
203 let value1 = ObjectValue::file(1, 0, now, now, now, now, 0, None);
204 let value2 = ObjectValue::file(2, 0, now, now, now, now, 0, None);
205
206 let placeholder = match cache.lookup_or_reserve(&key) {
207 ObjectCacheResult::Placeholder(placeholder) => placeholder,
208 _ => panic!("Expected cache miss with placeholder returned."),
209 };
210 placeholder.complete(Some(&value1));
211
212 let result = match cache.lookup_or_reserve(&key) {
213 ObjectCacheResult::Value(value) => value,
214 _ => panic!("Expected to find item."),
215 };
216 assert_eq!(&result, &value1);
217
218 cache.invalidate(key.clone(), Some(value2.clone()));
219
220 let result = match cache.lookup_or_reserve(&key) {
221 ObjectCacheResult::Value(value) => value,
222 _ => panic!("Expected to find item."),
223 };
224 assert_eq!(&result, &value2);
225 }
226
227 #[fuchsia::test]
228 async fn test_no_caching_for_filtered_item() {
229 let cache = TreeCache::new();
230 let key = ObjectKey::extent(1, 1, 1..2);
231
232 assert!(matches!(cache.lookup_or_reserve(&key), ObjectCacheResult::NoCache));
233 }
234
235 #[fuchsia::test]
238 async fn test_two_parallel_clients() {
239 let cache = TreeCache::new();
240 let key = ObjectKey::object(1);
241 let now = Timestamp::now();
242 let value1 = ObjectValue::file(1, 0, now, now, now, now, 0, None);
243 let value2 = ObjectValue::file(2, 0, now, now, now, now, 0, None);
244
245 let placeholder1 = match cache.lookup_or_reserve(&key) {
246 ObjectCacheResult::Placeholder(placeholder) => placeholder,
247 _ => panic!("Expected cache miss with placeholder returned."),
248 };
249
250 assert!(matches!(cache.lookup_or_reserve(&key), ObjectCacheResult::NoCache));
252
253 cache.invalidate(key.clone(), None);
255
256 let placeholder2 = match cache.lookup_or_reserve(&key) {
258 ObjectCacheResult::Placeholder(placeholder) => placeholder,
259 _ => panic!("Expected cache miss with placeholder returned."),
260 };
261
262 placeholder2.complete(Some(&value2));
264 placeholder1.complete(Some(&value1));
265
266 let result = match cache.lookup_or_reserve(&key) {
268 ObjectCacheResult::Value(value) => value,
269 _ => panic!("Expected to find item."),
270 };
271 assert_eq!(&result, &value2);
272 }
273}