// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 //! RAII block holding for sessions. //! //! This module provides [`BlockHolder`], a tier-agnostic container for //! holding blocks during session operations. Blocks are automatically //! released when the holder is dropped. //! //! # Design Philosophy //! //! `BlockHolder` is intentionally simple - it's pure RAII with no staging logic. //! This allows flexibility for different staging patterns: //! - G3→G2 staging //! - G4→G2 staging //! - G1→G2 staging //! - G2→G3 offload //! //! The caller decides when and how to stage; `BlockHolder` just holds. use crate::SequenceHash; use kvbm_logical::blocks::{BlockMetadata, ImmutableBlock}; /// RAII block holder - tier-agnostic, just holds blocks. /// /// # Type Parameter /// /// `T` is the tier metadata type (e.g., `G2`, `G3`). It must implement /// `BlockMetadata` which is `Clone + Send + Sync + 'static`. /// /// # RAII Semantics /// /// When `BlockHolder` is dropped, all held blocks are released. This ensures /// blocks don't leak even if session handling panics. /// /// # Example /// /// ```ignore /// // Create holder with searched blocks /// let mut holder = BlockHolder::new(g2_blocks); /// /// // Check what we have /// println!("Holding {} blocks", holder.count()); /// /// // Release some blocks (e.g., after RDMA pull) /// holder.release(&pulled_hashes); /// /// // Holder drops here, releasing any remaining blocks /// ``` #[derive(Debug)] pub struct BlockHolder { blocks: Vec>, } impl BlockHolder { /// Create a new `BlockHolder` with the given blocks. pub fn new(blocks: Vec>) -> Self { Self { blocks } } /// Create an empty `BlockHolder`. pub fn empty() -> Self { Self { blocks: Vec::new() } } /// Get a reference to the held blocks. pub fn blocks(&self) -> &[ImmutableBlock] { &self.blocks } /// Get the number of held blocks. pub fn count(&self) -> usize { self.blocks.len() } /// Check if the holder is empty. pub fn is_empty(&self) -> bool { self.blocks.is_empty() } /// Add blocks to this holder. pub fn extend(&mut self, blocks: impl IntoIterator>) { self.blocks.extend(blocks); } /// Release blocks matching the given sequence hashes. /// /// Removes blocks from the holder whose sequence hash is in `hashes`. /// The blocks are dropped, releasing their references. pub fn release(&mut self, hashes: &[SequenceHash]) { self.blocks.retain(|b| !hashes.contains(&b.sequence_hash())); } /// Retain only blocks matching the given sequence hashes. /// /// Removes blocks from the holder whose sequence hash is NOT in `hashes`. /// The removed blocks are dropped, releasing their references. pub fn retain(&mut self, hashes: &[SequenceHash]) { self.blocks.retain(|b| hashes.contains(&b.sequence_hash())); } /// Take all blocks out of this holder. /// /// The holder becomes empty. Useful for transferring blocks to another /// location or for processing before dropping. pub fn take_all(&mut self) -> Vec> { std::mem::take(&mut self.blocks) } /// Get sequence hashes of all held blocks. pub fn sequence_hashes(&self) -> Vec { self.blocks.iter().map(|b| b.sequence_hash()).collect() } /// Find a block by sequence hash. pub fn find(&self, hash: &SequenceHash) -> Option<&ImmutableBlock> { self.blocks.iter().find(|b| &b.sequence_hash() == hash) } /// Check if a block with the given hash is held. pub fn contains(&self, hash: &SequenceHash) -> bool { self.blocks.iter().any(|b| &b.sequence_hash() == hash) } /// Iterate over held blocks. pub fn iter(&self) -> impl Iterator> { self.blocks.iter() } } impl Default for BlockHolder { fn default() -> Self { Self::empty() } } impl FromIterator> for BlockHolder { fn from_iter>>(iter: I) -> Self { Self::new(iter.into_iter().collect()) } } impl IntoIterator for BlockHolder { type Item = ImmutableBlock; type IntoIter = std::vec::IntoIter>; fn into_iter(self) -> Self::IntoIter { self.blocks.into_iter() } } impl<'a, T: BlockMetadata> IntoIterator for &'a BlockHolder { type Item = &'a ImmutableBlock; type IntoIter = std::slice::Iter<'a, ImmutableBlock>; fn into_iter(self) -> Self::IntoIter { self.blocks.iter() } } #[cfg(test)] mod tests { use super::*; // Note: Full tests require test infrastructure to create ImmutableBlock instances. // These tests verify the basic container operations. #[test] fn test_empty_holder() { let holder: BlockHolder<()> = BlockHolder::empty(); assert!(holder.is_empty()); assert_eq!(holder.count(), 0); } #[test] fn test_default_is_empty() { let holder: BlockHolder<()> = BlockHolder::default(); assert!(holder.is_empty()); } }