kvbm_logical/manager/
mod.rs1mod builder;
11
12#[cfg(test)]
13mod tests;
14
15pub use builder::{
16 BlockManagerBuilderError, BlockManagerConfigBuilder, BlockManagerResetError,
17 FrequencyTrackingCapacity, InactiveBackendConfig, LineageEviction,
18};
19
20use std::collections::HashMap;
21use std::sync::Arc;
22
23use crate::blocks::{BlockMetadata, CompleteBlock, ImmutableBlock, MutableBlock};
24use crate::metrics::BlockPoolMetrics;
25use crate::pools::{BlockDuplicationPolicy, BlockStore, SequenceHash};
26use crate::registry::BlockRegistry;
27
28pub struct BlockManager<T: BlockMetadata> {
32 pub(crate) store: Arc<BlockStore<T>>,
33 pub(crate) block_registry: BlockRegistry,
34 pub(crate) duplication_policy: BlockDuplicationPolicy,
35 pub(crate) total_blocks: usize,
36 pub(crate) block_size: usize,
37 pub(crate) metrics: Arc<BlockPoolMetrics>,
38}
39
40impl<T: BlockMetadata + Sync> BlockManager<T> {
41 pub fn builder() -> BlockManagerConfigBuilder<T> {
43 BlockManagerConfigBuilder::default()
44 }
45
46 pub fn id(&self) -> crate::ManagerId {
55 self.store.id()
56 }
57
58 pub fn allocate_blocks(&self, count: usize) -> Option<Vec<MutableBlock<T>>> {
63 self.allocate_blocks_with_evictions(count)
64 .map(|(blocks, _evicted)| blocks)
65 }
66
67 pub fn allocate_blocks_with_evictions(
70 &self,
71 count: usize,
72 ) -> Option<(Vec<MutableBlock<T>>, Vec<SequenceHash>)> {
73 self.store.allocate_atomic(count)
74 }
75
76 pub fn reset_inactive_pool(&self) -> Result<(), BlockManagerResetError> {
78 let blocks = self.store.drain_inactive_to_mutable();
79 drop(blocks);
80
81 let reset_count = self.store.reset_len();
82 if reset_count != self.total_blocks {
83 return Err(BlockManagerResetError::BlockCountMismatch {
84 expected: self.total_blocks,
85 actual: reset_count,
86 });
87 }
88
89 Ok(())
90 }
91
92 pub fn register_blocks(&self, blocks: Vec<CompleteBlock<T>>) -> Vec<ImmutableBlock<T>> {
94 if blocks.is_empty() {
95 return Vec::new();
96 }
97
98 let handles = self
99 .block_registry
100 .register_sequence_hashes(blocks.iter().map(CompleteBlock::sequence_hash));
101 let batch_size = blocks.len();
102 let registered = self.store.register_completed_blocks(
103 blocks.into_iter().zip(handles).collect(),
104 self.duplication_policy,
105 );
106 self.metrics
110 .inc_registrations_by(u64::try_from(batch_size).unwrap_or(u64::MAX));
111 registered
112 .into_iter()
113 .map(ImmutableBlock::from_inner)
114 .collect()
115 }
116
117 pub fn register_block(&self, block: CompleteBlock<T>) -> ImmutableBlock<T> {
119 let handle = self
120 .block_registry
121 .register_sequence_hash(block.sequence_hash());
122 let inner = handle.register_block(block, self.duplication_policy, &self.store);
123 self.metrics.inc_registrations();
124 ImmutableBlock::from_inner(inner)
125 }
126
127 pub fn match_blocks(&self, seq_hash: &[SequenceHash]) -> Vec<ImmutableBlock<T>> {
137 self.metrics
138 .inc_match_hashes_requested(seq_hash.len() as u64);
139
140 if seq_hash.is_empty() {
141 self.metrics.inc_match_blocks_returned(0);
142 return Vec::new();
143 }
144
145 let inners = self.store.match_prefix_locked_batch(seq_hash);
147
148 if self.block_registry.has_frequency_tracking() {
153 for inner in &inners {
154 self.block_registry.touch(inner.sequence_hash());
155 }
156 }
157
158 let matched: Vec<ImmutableBlock<T>> =
159 inners.into_iter().map(ImmutableBlock::from_inner).collect();
160
161 self.metrics.inc_match_blocks_returned(matched.len() as u64);
162 tracing::debug!(
163 num_hashes = seq_hash.len(),
164 total_matched = matched.len(),
165 "match_blocks result"
166 );
167 tracing::trace!(matched = ?matched, "matched blocks");
168 matched
169 }
170
171 pub fn scan_matches(
174 &self,
175 seq_hashes: &[SequenceHash],
176 touch: bool,
177 ) -> HashMap<SequenceHash, ImmutableBlock<T>> {
178 self.metrics
179 .inc_scan_hashes_requested(seq_hashes.len() as u64);
180
181 let mut result = HashMap::new();
182
183 let active_found = self.scan_active_matches(seq_hashes, touch);
184 for (hash, inner) in active_found {
185 result.insert(hash, ImmutableBlock::from_inner(inner));
186 }
187
188 let remaining: Vec<SequenceHash> = seq_hashes
189 .iter()
190 .filter(|h| !result.contains_key(h))
191 .copied()
192 .collect();
193
194 if !remaining.is_empty() {
195 let inactive_found = self.store.scan_inactive_primaries(&remaining, touch);
196 for (hash, inner) in inactive_found {
197 result.insert(hash, ImmutableBlock::from_inner(inner));
198 }
199 }
200
201 self.metrics.inc_scan_blocks_returned(result.len() as u64);
202
203 result
204 }
205
206 fn scan_active_matches(
209 &self,
210 hashes: &[SequenceHash],
211 touch: bool,
212 ) -> Vec<(SequenceHash, Arc<crate::blocks::ImmutableBlockInner<T>>)> {
213 hashes
214 .iter()
215 .filter_map(|hash| {
216 self.block_registry
217 .match_sequence_hash(*hash, touch)
218 .and_then(|handle| {
219 handle
220 .try_get_inner::<T>(&self.store, touch)
221 .map(|inner| (*hash, inner))
222 })
223 })
224 .collect()
225 }
226
227 pub fn total_blocks(&self) -> usize {
229 self.total_blocks
230 }
231
232 pub fn available_blocks(&self) -> usize {
238 self.store.available_len()
239 }
240
241 pub fn block_size(&self) -> usize {
243 self.block_size
244 }
245
246 pub fn duplication_policy(&self) -> &BlockDuplicationPolicy {
248 &self.duplication_policy
249 }
250
251 pub fn block_registry(&self) -> &BlockRegistry {
253 &self.block_registry
254 }
255
256 pub fn metrics(&self) -> &Arc<BlockPoolMetrics> {
258 &self.metrics
259 }
260
261 #[cfg(test)]
265 pub(crate) fn store_for_test(&self) -> &Arc<BlockStore<T>> {
266 &self.store
267 }
268}