blocks.rs 5.3 KB
Newer Older
Ryan Olson's avatar
Ryan Olson committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
// 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<T>`], 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<T: BlockMetadata> {
    blocks: Vec<ImmutableBlock<T>>,
}

impl<T: BlockMetadata> BlockHolder<T> {
    /// Create a new `BlockHolder` with the given blocks.
    pub fn new(blocks: Vec<ImmutableBlock<T>>) -> 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<T>] {
        &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<Item = ImmutableBlock<T>>) {
        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<ImmutableBlock<T>> {
        std::mem::take(&mut self.blocks)
    }

    /// Get sequence hashes of all held blocks.
    pub fn sequence_hashes(&self) -> Vec<SequenceHash> {
        self.blocks.iter().map(|b| b.sequence_hash()).collect()
    }

    /// Find a block by sequence hash.
    pub fn find(&self, hash: &SequenceHash) -> Option<&ImmutableBlock<T>> {
        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<Item = &ImmutableBlock<T>> {
        self.blocks.iter()
    }
}

impl<T: BlockMetadata> Default for BlockHolder<T> {
    fn default() -> Self {
        Self::empty()
    }
}

impl<T: BlockMetadata> FromIterator<ImmutableBlock<T>> for BlockHolder<T> {
    fn from_iter<I: IntoIterator<Item = ImmutableBlock<T>>>(iter: I) -> Self {
        Self::new(iter.into_iter().collect())
    }
}

impl<T: BlockMetadata> IntoIterator for BlockHolder<T> {
    type Item = ImmutableBlock<T>;
    type IntoIter = std::vec::IntoIter<ImmutableBlock<T>>;

    fn into_iter(self) -> Self::IntoIter {
        self.blocks.into_iter()
    }
}

impl<'a, T: BlockMetadata> IntoIterator for &'a BlockHolder<T> {
    type Item = &'a ImmutableBlock<T>;
    type IntoIter = std::slice::Iter<'a, ImmutableBlock<T>>;

    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());
    }
}