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}