Skip to main content

kvbm_engine/leader/session/
blocks.rs

1// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! RAII block holding for sessions.
5//!
6//! This module provides [`BlockHolder<T>`], a tier-agnostic container for
7//! holding blocks during session operations. Blocks are automatically
8//! released when the holder is dropped.
9//!
10//! # Design Philosophy
11//!
12//! `BlockHolder` is intentionally simple - it's pure RAII with no staging logic.
13//! This allows flexibility for different staging patterns:
14//! - G3→G2 staging
15//! - G4→G2 staging
16//! - G1→G2 staging
17//! - G2→G3 offload
18//!
19//! The caller decides when and how to stage; `BlockHolder` just holds.
20
21use crate::SequenceHash;
22use kvbm_logical::blocks::{BlockMetadata, ImmutableBlock};
23
24/// RAII block holder - tier-agnostic, just holds blocks.
25///
26/// # Type Parameter
27///
28/// `T` is the tier metadata type (e.g., `G2`, `G3`). It must implement
29/// `BlockMetadata` which is `Clone + Send + Sync + 'static`.
30///
31/// # RAII Semantics
32///
33/// When `BlockHolder` is dropped, all held blocks are released. This ensures
34/// blocks don't leak even if session handling panics.
35///
36/// # Example
37///
38/// ```ignore
39/// // Create holder with searched blocks
40/// let mut holder = BlockHolder::new(g2_blocks);
41///
42/// // Check what we have
43/// println!("Holding {} blocks", holder.count());
44///
45/// // Release some blocks (e.g., after RDMA pull)
46/// holder.release(&pulled_hashes);
47///
48/// // Holder drops here, releasing any remaining blocks
49/// ```
50#[derive(Debug)]
51pub struct BlockHolder<T: BlockMetadata> {
52    blocks: Vec<ImmutableBlock<T>>,
53}
54
55impl<T: BlockMetadata> BlockHolder<T> {
56    /// Create a new `BlockHolder` with the given blocks.
57    pub fn new(blocks: Vec<ImmutableBlock<T>>) -> Self {
58        Self { blocks }
59    }
60
61    /// Create an empty `BlockHolder`.
62    pub fn empty() -> Self {
63        Self { blocks: Vec::new() }
64    }
65
66    /// Get a reference to the held blocks.
67    pub fn blocks(&self) -> &[ImmutableBlock<T>] {
68        &self.blocks
69    }
70
71    /// Get the number of held blocks.
72    pub fn count(&self) -> usize {
73        self.blocks.len()
74    }
75
76    /// Check if the holder is empty.
77    pub fn is_empty(&self) -> bool {
78        self.blocks.is_empty()
79    }
80
81    /// Add blocks to this holder.
82    pub fn extend(&mut self, blocks: impl IntoIterator<Item = ImmutableBlock<T>>) {
83        self.blocks.extend(blocks);
84    }
85
86    /// Release blocks matching the given sequence hashes.
87    ///
88    /// Removes blocks from the holder whose sequence hash is in `hashes`.
89    /// The blocks are dropped, releasing their references.
90    pub fn release(&mut self, hashes: &[SequenceHash]) {
91        self.blocks.retain(|b| !hashes.contains(&b.sequence_hash()));
92    }
93
94    /// Retain only blocks matching the given sequence hashes.
95    ///
96    /// Removes blocks from the holder whose sequence hash is NOT in `hashes`.
97    /// The removed blocks are dropped, releasing their references.
98    pub fn retain(&mut self, hashes: &[SequenceHash]) {
99        self.blocks.retain(|b| hashes.contains(&b.sequence_hash()));
100    }
101
102    /// Take all blocks out of this holder.
103    ///
104    /// The holder becomes empty. Useful for transferring blocks to another
105    /// location or for processing before dropping.
106    pub fn take_all(&mut self) -> Vec<ImmutableBlock<T>> {
107        std::mem::take(&mut self.blocks)
108    }
109
110    /// Get sequence hashes of all held blocks.
111    pub fn sequence_hashes(&self) -> Vec<SequenceHash> {
112        self.blocks.iter().map(|b| b.sequence_hash()).collect()
113    }
114
115    /// Find a block by sequence hash.
116    pub fn find(&self, hash: &SequenceHash) -> Option<&ImmutableBlock<T>> {
117        self.blocks.iter().find(|b| &b.sequence_hash() == hash)
118    }
119
120    /// Check if a block with the given hash is held.
121    pub fn contains(&self, hash: &SequenceHash) -> bool {
122        self.blocks.iter().any(|b| &b.sequence_hash() == hash)
123    }
124
125    /// Iterate over held blocks.
126    pub fn iter(&self) -> impl Iterator<Item = &ImmutableBlock<T>> {
127        self.blocks.iter()
128    }
129}
130
131impl<T: BlockMetadata> Default for BlockHolder<T> {
132    fn default() -> Self {
133        Self::empty()
134    }
135}
136
137impl<T: BlockMetadata> FromIterator<ImmutableBlock<T>> for BlockHolder<T> {
138    fn from_iter<I: IntoIterator<Item = ImmutableBlock<T>>>(iter: I) -> Self {
139        Self::new(iter.into_iter().collect())
140    }
141}
142
143impl<T: BlockMetadata> IntoIterator for BlockHolder<T> {
144    type Item = ImmutableBlock<T>;
145    type IntoIter = std::vec::IntoIter<ImmutableBlock<T>>;
146
147    fn into_iter(self) -> Self::IntoIter {
148        self.blocks.into_iter()
149    }
150}
151
152impl<'a, T: BlockMetadata> IntoIterator for &'a BlockHolder<T> {
153    type Item = &'a ImmutableBlock<T>;
154    type IntoIter = std::slice::Iter<'a, ImmutableBlock<T>>;
155
156    fn into_iter(self) -> Self::IntoIter {
157        self.blocks.iter()
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use super::*;
164
165    // Note: Full tests require test infrastructure to create ImmutableBlock instances.
166    // These tests verify the basic container operations.
167
168    #[test]
169    fn test_empty_holder() {
170        let holder: BlockHolder<()> = BlockHolder::empty();
171        assert!(holder.is_empty());
172        assert_eq!(holder.count(), 0);
173    }
174
175    #[test]
176    fn test_default_is_empty() {
177        let holder: BlockHolder<()> = BlockHolder::default();
178        assert!(holder.is_empty());
179    }
180}