1mod any;
4pub mod forest;
5mod many;
6pub mod plain;
7
8pub use any::AnyCar;
9pub use forest::ForestCar;
10use get_size2::GetSize as _;
11pub use many::{ManyCar, ReloadableManyCar};
12pub use plain::PlainCar;
13
14use bytes::Bytes;
15use positioned_io::{ReadAt, Size};
16use std::{num::NonZeroUsize, sync::LazyLock};
17
18use crate::prelude::*;
19use quick_cache::Weighter;
20use quick_cache::sync::Cache as QuickCache;
21
22pub trait RandomAccessFileReader: ReadAt + Size + Send + Sync + 'static {}
23impl<X: ReadAt + Size + Send + Sync + 'static> RandomAccessFileReader for X {}
24
25pub type CacheKey = u64;
28
29type FrameOffset = u64;
30
31pub const V2_SNAPSHOT_ROOT_COUNT: usize = 1;
33
34pub static ZSTD_FRAME_CACHE_DEFAULT_MAX_SIZE: LazyLock<usize> = LazyLock::new(|| {
35 const ENV_KEY: &str = "FOREST_ZSTD_FRAME_CACHE_DEFAULT_MAX_SIZE";
36 if let Ok(value) = std::env::var(ENV_KEY) {
37 if let Ok(size) = value.parse::<NonZeroUsize>() {
38 let size = size.get();
39 tracing::info!("zstd frame max size is set to {size} via {ENV_KEY}");
40 return size;
41 } else {
42 tracing::error!(
43 "Failed to parse {ENV_KEY}={value}, value should be a positive integer"
44 );
45 }
46 }
47 512 * 1024 * 1024
49});
50
51type FrameIndex = Arc<hashbrown::HashMap<CidWrapper, Bytes>>;
55
56#[derive(Clone, Copy, Debug, Default)]
59pub struct ZstdFrameWeighter;
60
61impl Weighter<(FrameOffset, CacheKey), FrameIndex> for ZstdFrameWeighter {
62 fn weight(&self, key: &(FrameOffset, CacheKey), value: &FrameIndex) -> u64 {
63 (key.get_size().saturating_add(value.get_size()) as u64).max(1)
66 }
67}
68
69type ZstdFrameInner = QuickCache<(FrameOffset, CacheKey), FrameIndex, ZstdFrameWeighter>;
70
71#[derive(derive_more::Deref)]
72pub struct ZstdFrameCache {
73 pub max_size: usize,
76 #[deref]
77 cache: Arc<ZstdFrameInner>,
78}
79
80impl ShallowClone for ZstdFrameCache {
81 fn shallow_clone(&self) -> Self {
82 Self {
83 max_size: self.max_size,
84 cache: self.cache.shallow_clone(),
85 }
86 }
87}
88
89impl Default for ZstdFrameCache {
90 fn default() -> Self {
91 ZstdFrameCache::new(*ZSTD_FRAME_CACHE_DEFAULT_MAX_SIZE)
92 }
93}
94
95impl ZstdFrameCache {
96 pub fn new(max_size: usize) -> Self {
97 const ESTIMATED_ITEMS: usize = 64;
102 ZstdFrameCache {
103 max_size,
104 cache: Arc::new(QuickCache::with_weighter(
105 ESTIMATED_ITEMS,
106 max_size as u64,
107 ZstdFrameWeighter,
108 )),
109 }
110 }
111
112 pub fn get(&self, offset: FrameOffset, key: CacheKey, cid: Cid) -> Option<Option<Bytes>> {
115 self.cache
116 .get(&(offset, key))
117 .map(|index| index.get(&CidWrapper::from(cid)).cloned())
118 }
119
120 pub fn put(
123 &self,
124 offset: FrameOffset,
125 key: CacheKey,
126 mut index: hashbrown::HashMap<CidWrapper, Bytes>,
127 ) {
128 index.shrink_to_fit();
129
130 let cache_key = (offset, key);
131 let cache_key_size = cache_key.get_size();
132 let entry_size = index.get_size();
133 if entry_size.saturating_add(cache_key_size) > self.max_size {
136 return;
137 }
138 self.insert(cache_key, Arc::new(index));
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145 use crate::utils::{multihash::MultihashCode, rand::forest_rng};
146 use fvm_ipld_encoding::IPLD_RAW;
147 use multihash_derive::MultihashDigest;
148 use rand::Rng;
149
150 #[test]
151 fn test_zstd_frame_cache_stays_under_max_size() {
152 let mut rng = forest_rng();
153 let max_size: usize = 64 * 1024;
155 let cache = ZstdFrameCache::new(max_size);
156 for i in 0..100 {
157 cache.put(i, i, gen_index(&mut rng));
158 assert!(
161 cache.weight() <= max_size as u64,
162 "weight {} exceeds cap {}",
163 cache.weight(),
164 max_size
165 );
166 }
167 assert!(cache.len() < 100);
170 }
171
172 fn gen_index(rng: &mut impl Rng) -> hashbrown::HashMap<CidWrapper, Bytes> {
173 let mut map = hashbrown::HashMap::default();
174 for _ in 0..10 {
175 let vec_len = rng.gen_range(64..1024);
176 let mut data = vec![0; vec_len];
177 rng.fill_bytes(&mut data);
178 let cid = Cid::new_v1(IPLD_RAW, MultihashCode::Blake2b256.digest(&data));
179 map.insert(cid.into(), data.into());
180 }
181 map
182 }
183}