cnidarium/
snapshot_cache.rs1use crate::Snapshot;
2use std::{cmp, collections::VecDeque};
3
4pub struct SnapshotCache {
17 cache: VecDeque<Snapshot>,
19 max_size: usize,
21}
22
23impl SnapshotCache {
24 pub fn new(initial: Snapshot, max_size: usize) -> Self {
27 let max_size = cmp::max(max_size, 1);
28 let mut cache = VecDeque::with_capacity(max_size);
29 cache.push_front(initial);
30
31 Self { cache, max_size }
32 }
33
34 pub fn try_push(&mut self, snapshot: Snapshot) -> anyhow::Result<()> {
48 let latest_version = self.latest().version();
49 if latest_version.wrapping_add(1) != snapshot.version() {
50 anyhow::bail!("snapshot_cache: trying to insert stale snapshots.");
51 }
52
53 if self.cache.len() >= self.max_size {
54 self.cache.pop_back();
55 }
56
57 self.cache.push_front(snapshot);
58 Ok(())
59 }
60
61 pub fn latest(&self) -> Snapshot {
63 self.cache
64 .front()
65 .map(Clone::clone)
66 .expect("snapshot_cache cannot be empty")
67 }
68
69 pub fn get(&self, version: jmt::Version) -> Option<Snapshot> {
72 let latest_version = self.latest().version();
73 let offset = latest_version.wrapping_sub(version) as usize;
76 self.cache
77 .get(offset)
78 .map(Clone::clone)
79 .filter(|s| s.version() == version)
80 }
81
82 pub fn clear(&mut self) {
84 self.cache.clear();
85 }
86}
87
88#[cfg(test)]
89mod test {
90
91 use crate::snapshot::Snapshot;
92 use crate::snapshot_cache::SnapshotCache;
93 use crate::storage::Storage;
94 use crate::store::multistore::MultistoreCache;
95
96 async fn create_storage_instance() -> Storage {
97 use tempfile::tempdir;
98 let dir = tempdir().expect("unable to create tempdir");
100 let file_path = dir.path().join("snapshot-cache-testing.db");
101
102 Storage::load(file_path, vec![])
103 .await
104 .expect("unable to load storage")
105 }
106
107 #[tokio::test]
108 async fn fail_zero_capacity() {
110 let storage = create_storage_instance().await;
111 let db = storage.db();
112 let snapshot = storage.latest_snapshot();
113 let mut cache = SnapshotCache::new(snapshot, 0);
114
115 assert!(cache.get(u64::MAX).is_some());
117 let new_snapshot = Snapshot::new(db, 0, MultistoreCache::default());
118 cache
119 .try_push(new_snapshot)
120 .expect("should not fail to insert a new entry");
121
122 assert!(cache.get(u64::MAX).is_none());
124 assert!(cache.get(0).is_some());
125 }
126
127 #[tokio::test]
128 async fn fail_insert_stale_snapshot() {
130 let storage = create_storage_instance().await;
131 let db_handle = storage.db();
132 let snapshot = storage.latest_snapshot();
133 let mut cache = SnapshotCache::new(snapshot, 1);
134 let stale_snapshot = Snapshot::new(db_handle, 1, MultistoreCache::default());
135 cache
136 .try_push(stale_snapshot)
137 .expect_err("should fail to insert a stale entry in the snapshot cache");
138 }
139
140 #[tokio::test]
141 async fn fail_insert_gapped_snapshot() {
143 let storage = create_storage_instance().await;
144 let db_handle = storage.db();
145 let snapshot = Snapshot::new(db_handle.clone(), 0, MultistoreCache::default());
146 let mut cache = SnapshotCache::new(snapshot, 2);
147 let snapshot = Snapshot::new(db_handle, 2, MultistoreCache::default());
148 cache
149 .try_push(snapshot)
150 .expect_err("should fail to insert snapshot with skipped version number");
151 }
152
153 #[tokio::test]
154 async fn cache_manage_pre_genesis() {
156 let storage = create_storage_instance().await;
157 let db_handle = storage.db();
158 let snapshot = storage.latest_snapshot();
159
160 let mut cache = SnapshotCache::new(snapshot, 10);
162
163 for i in 0..9 {
165 let snapshot = Snapshot::new(db_handle.clone(), i, MultistoreCache::default());
166 cache
167 .try_push(snapshot)
168 .expect("should not fail to insert a new entry");
169 }
170
171 assert!(cache.get(u64::MAX).is_some());
173
174 let new_snapshot = Snapshot::new(db_handle, 9, MultistoreCache::default());
177 cache
178 .try_push(new_snapshot)
179 .expect("should not fail to insert a new entry");
180
181 assert!(cache.get(u64::MAX).is_none());
183
184 for i in 0..10 {
186 assert!(cache.get(i).is_some());
187 }
188 }
189
190 #[tokio::test]
191 async fn drop_oldest_snapshot() {
193 let storage = create_storage_instance().await;
194 let db_handle = storage.db();
195 let snapshot = Snapshot::new(db_handle.clone(), 0, MultistoreCache::default());
196
197 let mut cache = SnapshotCache::new(snapshot, 10);
199
200 for i in 1..10 {
202 let snapshot = Snapshot::new(db_handle.clone(), i, MultistoreCache::default());
203 cache
204 .try_push(snapshot)
205 .expect("should be able to insert new entries")
206 }
207
208 assert!(cache.get(0).is_some());
210
211 let snapshot = Snapshot::new(db_handle, 10, MultistoreCache::default());
213 cache
214 .try_push(snapshot)
215 .expect("should be able to insert a new entry");
216
217 assert!(cache.get(0).is_none());
219
220 assert_eq!(cache.latest().version(), 10);
222
223 for i in 1..11 {
225 assert!(cache.get(i).is_some());
226 }
227 }
228}