1use std::num::NonZeroUsize;
7use std::sync::Arc;
8
9use crate::metrics::{BlockPoolMetrics, MetricsAggregator, short_type_name};
10use crate::tinylfu::TinyLFUTracker;
11
12use crate::{
13 blocks::BlockMetadata,
14 pools::{
15 BlockDuplicationPolicy, BlockStore, InactiveIndex,
16 backends::{
17 FifoReusePolicy, HashMapBackend, LeafPolicy, LineageBackend, LruBackend,
18 MultiLruBackend,
19 },
20 },
21 registry::BlockRegistry,
22};
23
24use super::BlockManager;
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
29pub enum FrequencyTrackingCapacity {
30 Small,
32 #[default]
34 Medium,
35 Large,
37}
38
39impl FrequencyTrackingCapacity {
40 pub fn size(&self) -> usize {
42 match self {
43 Self::Small => 1 << 18,
44 Self::Medium => 1 << 21,
45 Self::Large => 1 << 24,
46 }
47 }
48
49 pub fn create_tracker(&self) -> Arc<TinyLFUTracker<u128>> {
51 Arc::new(TinyLFUTracker::new(self.size()))
52 }
53}
54
55pub enum InactiveBackendConfig {
57 HashMap,
59 Lru,
61 MultiLru {
63 frequency_thresholds: [u8; 3],
66 },
67 Lineage {
69 eviction: LineageEviction,
71 },
72}
73
74#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
77pub enum LineageEviction {
78 #[default]
82 Tick,
83 Fifo,
86}
87
88fn lineage_leaf_policy(eviction: LineageEviction, capacity: usize) -> LeafPolicy {
90 match eviction {
91 LineageEviction::Tick => LeafPolicy::tick(capacity),
92 LineageEviction::Fifo => LeafPolicy::fifo(capacity),
93 }
94}
95
96#[derive(Debug, thiserror::Error)]
98pub enum BlockManagerBuilderError {
99 #[error("Block count must be greater than 0")]
100 InvalidBlockCount,
101 #[error("Block size mismatch: expected {expected} tokens, got {actual}")]
102 BlockSizeMismatch { expected: usize, actual: usize },
103 #[error("Invalid backend configuration: {0}")]
104 InvalidBackend(String),
105 #[error("Builder validation failed: {0}")]
106 ValidationError(String),
107}
108
109#[derive(Debug, thiserror::Error)]
111pub enum BlockManagerResetError {
112 #[error("Reset pool count mismatch: expected {expected}, got {actual}")]
113 BlockCountMismatch { expected: usize, actual: usize },
114}
115
116pub struct BlockManagerConfigBuilder<T: BlockMetadata> {
120 block_count: Option<usize>,
122
123 block_size: Option<usize>,
126
127 registry: Option<BlockRegistry>,
129
130 inactive_backend: Option<InactiveBackendConfig>,
132
133 duplication_policy: Option<BlockDuplicationPolicy>,
135
136 aggregator: Option<MetricsAggregator>,
138
139 default_reset_on_release: Option<bool>,
145
146 _phantom: std::marker::PhantomData<T>,
148}
149
150impl<T: BlockMetadata> Default for BlockManagerConfigBuilder<T> {
151 fn default() -> Self {
152 Self {
153 block_count: None,
154 block_size: Some(16), registry: None,
156 inactive_backend: None,
157 duplication_policy: None,
158 aggregator: None,
159 default_reset_on_release: None,
160 _phantom: std::marker::PhantomData,
161 }
162 }
163}
164
165impl<T: BlockMetadata> BlockManagerConfigBuilder<T> {
166 pub fn new() -> Self {
168 Self::default()
169 }
170
171 pub fn block_count(mut self, count: usize) -> Self {
173 self.block_count = Some(count);
174 self
175 }
176
177 pub fn block_size(mut self, size: usize) -> Self {
186 assert!(
187 (1..=1024).contains(&size),
188 "block_size must be between 1 and 1024, got {}",
189 size
190 );
191 assert!(
192 size.is_power_of_two(),
193 "block_size must be a power of 2, got {}",
194 size
195 );
196 self.block_size = Some(size);
197 self
198 }
199
200 pub fn duplication_policy(mut self, policy: BlockDuplicationPolicy) -> Self {
202 self.duplication_policy = Some(policy);
203 self
204 }
205
206 pub fn registry(mut self, registry: BlockRegistry) -> Self {
208 self.registry = Some(registry);
209 self
210 }
211
212 pub fn with_lru_backend(mut self) -> Self {
214 self.inactive_backend = Some(InactiveBackendConfig::Lru);
215 self
216 }
217
218 pub fn with_multi_lru_backend(mut self) -> Self {
223 self.inactive_backend = Some(InactiveBackendConfig::MultiLru {
224 frequency_thresholds: [3, 8, 15],
225 });
226 self
227 }
228
229 pub fn with_multi_lru_backend_custom_thresholds(
244 mut self,
245 cold_to_warm: u8,
246 warm_to_hot: u8,
247 hot_to_very_hot: u8,
248 ) -> Self {
249 assert!(
251 cold_to_warm < warm_to_hot && warm_to_hot < hot_to_very_hot,
252 "Thresholds must be in ascending order: {} < {} < {} failed",
253 cold_to_warm,
254 warm_to_hot,
255 hot_to_very_hot
256 );
257
258 assert!(
260 hot_to_very_hot <= 15,
261 "hot_to_very_hot threshold ({}) must be <= 15 (4-bit counter maximum)",
262 hot_to_very_hot
263 );
264
265 assert!(
267 cold_to_warm >= 1,
268 "cold_to_warm threshold must be >= 1 to distinguish from zero-access blocks"
269 );
270
271 self.inactive_backend = Some(InactiveBackendConfig::MultiLru {
272 frequency_thresholds: [cold_to_warm, warm_to_hot, hot_to_very_hot],
273 });
274 self
275 }
276
277 pub fn with_hashmap_backend(mut self) -> Self {
279 self.inactive_backend = Some(InactiveBackendConfig::HashMap);
280 self
281 }
282
283 pub fn with_lineage_backend(mut self) -> Self {
286 self.inactive_backend = Some(InactiveBackendConfig::Lineage {
287 eviction: LineageEviction::default(),
288 });
289 self
290 }
291
292 pub fn with_lineage_backend_eviction(mut self, eviction: LineageEviction) -> Self {
294 self.inactive_backend = Some(InactiveBackendConfig::Lineage { eviction });
295 self
296 }
297
298 pub fn aggregator(mut self, aggregator: MetricsAggregator) -> Self {
302 self.aggregator = Some(aggregator);
303 self
304 }
305
306 pub fn with_default_reset_on_release(mut self, value: bool) -> Self {
318 self.default_reset_on_release = Some(value);
319 self
320 }
321
322 fn validate(&self) -> Result<(), String> {
324 let registry = self.registry.as_ref().ok_or("registry is required")?;
325
326 let block_count = self.block_count.ok_or("block_count is required")?;
327
328 if block_count == 0 {
329 return Err("block_count must be greater than 0".to_string());
330 }
331
332 let block_size = self.block_size.unwrap_or(16);
334 if !block_size.is_power_of_two() || !(1..=1024).contains(&block_size) {
335 return Err(format!(
336 "Invalid block_size {}: must be a power of 2 between 1 and 1024",
337 block_size
338 ));
339 }
340
341 if let Some(InactiveBackendConfig::MultiLru {
343 frequency_thresholds,
344 }) = &self.inactive_backend
345 {
346 let [t1, t2, t3] = frequency_thresholds;
347 if !(*t1 < *t2 && *t2 < *t3) {
348 return Err(format!(
349 "Invalid thresholds [{}, {}, {}]: must be in ascending order",
350 t1, t2, t3
351 ));
352 }
353 if *t3 > 15 {
354 return Err(format!(
355 "Invalid threshold {}: maximum frequency is 15 (4-bit counter)",
356 t3
357 ));
358 }
359
360 if !registry.has_frequency_tracking() {
362 return Err(
363 "MultiLRU backend requires a registry with frequency tracking".to_string(),
364 );
365 }
366 }
367
368 Ok(())
369 }
370
371 pub fn build(mut self) -> Result<BlockManager<T>, BlockManagerBuilderError> {
377 self.validate()
379 .map_err(BlockManagerBuilderError::ValidationError)?;
380
381 let block_count = self.block_count.unwrap();
382 let block_size = self.block_size.unwrap_or(16);
383
384 let registry = self.registry.unwrap();
386
387 let metrics = Arc::new(BlockPoolMetrics::new(short_type_name::<T>()));
389
390 metrics.set_reset_pool_size(block_count as i64);
391
392 let backend: Box<dyn InactiveIndex> = match self.inactive_backend.take() {
394 Some(InactiveBackendConfig::HashMap) => {
395 tracing::info!("Using HashMap for inactive pool");
396 Box::new(HashMapBackend::new(Box::new(FifoReusePolicy::new())))
397 }
398 Some(InactiveBackendConfig::Lru) => {
399 let capacity = NonZeroUsize::new(block_count).expect("block_count must be > 0");
401 tracing::info!("Using LRU for inactive pool");
402 Box::new(LruBackend::new(capacity))
403 }
404 Some(InactiveBackendConfig::MultiLru {
405 frequency_thresholds,
406 }) => {
407 let frequency_tracker = registry.frequency_tracker().ok_or_else(|| {
409 BlockManagerBuilderError::InvalidBackend(
410 "MultiLRU backend requires a registry with frequency tracking".to_string(),
411 )
412 })?;
413
414 let level_capacity =
417 NonZeroUsize::new(block_count).expect("block_count must be > 0");
418
419 tracing::info!(
420 "Using MultiLRU inactive backend with thresholds: {:?}",
421 frequency_thresholds
422 );
423 Box::new(
424 MultiLruBackend::new_with_thresholds(
425 level_capacity,
426 &frequency_thresholds,
427 frequency_tracker,
428 )
429 .map_err(|e| BlockManagerBuilderError::InvalidBackend(e.to_string()))?,
430 )
431 }
432 Some(InactiveBackendConfig::Lineage { eviction }) => {
433 tracing::info!("Using Lineage inactive backend ({eviction:?})");
434 Box::new(LineageBackend::with_policy(
435 block_count,
436 lineage_leaf_policy(eviction, block_count),
437 ))
438 }
439 None => {
440 let eviction = LineageEviction::default();
441 tracing::info!("Using default inactive backend: Lineage ({eviction:?})");
442 Box::new(LineageBackend::with_policy(
443 block_count,
444 lineage_leaf_policy(eviction, block_count),
445 ))
446 }
447 };
448
449 let store = BlockStore::new(
451 block_count,
452 block_size,
453 backend,
454 metrics.clone(),
455 self.default_reset_on_release.unwrap_or(false),
456 );
457
458 if let Some(ref aggregator) = self.aggregator {
460 aggregator.register_source(metrics.clone());
461 }
462
463 Ok(BlockManager {
464 store,
465 block_registry: registry,
466 duplication_policy: self
467 .duplication_policy
468 .unwrap_or(BlockDuplicationPolicy::Allow),
469 total_blocks: block_count,
470 block_size,
471 metrics,
472 })
473 }
474}