Skip to main content

kvbm_logical/blocks/
mutable.rs

1// SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! RAII guard for a block in the **Reset** state.
5//!
6//! A [`MutableBlock`] is the entry point of the block lifecycle. It is
7//! obtained from
8//! [`BlockManager::allocate_blocks`](crate::manager::BlockManager::allocate_blocks)
9//! or by calling [`CompleteBlock::reset`](super::CompleteBlock::reset), and
10//! can be advanced to a [`CompleteBlock`](super::CompleteBlock) via
11//! [`stage`](MutableBlock::stage) or [`complete`](MutableBlock::complete).
12
13use std::sync::Arc;
14
15use dynamo_tokens::TokenBlock;
16
17use crate::KvbmSequenceHashProvider;
18use crate::blocks::{BlockError, BlockId, BlockMetadata, CompleteBlock, SequenceHash};
19use crate::pools::BlockStore;
20
21/// RAII guard for a block in the **Reset** state.
22///
23/// Holds an `Arc<BlockStore<T>>` and a `BlockId`; the slot at that id is in
24/// `SlotState::Mutable` while this guard exists. Drop returns the slot to
25/// the reset pool.
26///
27/// # Drop behaviour
28///
29/// Dropping a `MutableBlock` returns the slot to the reset pool. The store's
30/// `release_mutable` updates the `inflight_mutable` gauge.
31pub struct MutableBlock<T: BlockMetadata> {
32    store: Arc<BlockStore<T>>,
33    block_id: BlockId,
34    block_size: usize,
35    /// `false` once the guard has been consumed by a state-transition
36    /// method (`stage` / `complete`); Drop becomes a no-op.
37    armed: bool,
38}
39
40impl<T: BlockMetadata + Sync> MutableBlock<T> {
41    /// Build a new `MutableBlock` for a slot the store has just transitioned
42    /// to `Mutable`. The store has already incremented the `inflight_mutable`
43    /// gauge as part of the transition.
44    pub(crate) fn from_store(
45        store: Arc<BlockStore<T>>,
46        block_id: BlockId,
47        block_size: usize,
48    ) -> Self {
49        Self {
50            store,
51            block_id,
52            block_size,
53            armed: true,
54        }
55    }
56
57    /// Returns the [`BlockId`] assigned to this block.
58    pub fn block_id(&self) -> BlockId {
59        self.block_id
60    }
61
62    /// Returns the fixed block size of this block in tokens.
63    pub fn block_size(&self) -> usize {
64        self.block_size
65    }
66
67    /// Transition from **Reset** to **Staged** with a pre-computed
68    /// [`SequenceHash`] and an explicit `block_size` check.
69    ///
70    /// On size mismatch returns `Err(`[`BlockError::BlockSizeMismatch`]`)`
71    /// containing this `MutableBlock` so the caller can recover it.
72    pub fn stage(
73        mut self,
74        seq_hash: SequenceHash,
75        block_size: usize,
76    ) -> Result<CompleteBlock<T>, BlockError<MutableBlock<T>>> {
77        if block_size != self.block_size {
78            return Err(BlockError::BlockSizeMismatch {
79                expected: self.block_size,
80                actual: block_size,
81                block: self,
82            });
83        }
84        self.store.transition_to_staged(self.block_id, seq_hash);
85        let id = self.block_id;
86        let bsize = self.block_size;
87        let store = self.store.clone();
88        // Disarm so Drop is a no-op; the slot is now in Staged state.
89        self.armed = false;
90        drop(self);
91        Ok(CompleteBlock::from_store(store, id, bsize, seq_hash))
92    }
93
94    /// Transition from **Reset** to **Staged** by extracting the
95    /// [`SequenceHash`] from a [`TokenBlock`].
96    pub fn complete(
97        self,
98        token_block: &TokenBlock,
99    ) -> Result<CompleteBlock<T>, BlockError<MutableBlock<T>>> {
100        let actual = token_block.block_size();
101        if actual != self.block_size {
102            return Err(BlockError::BlockSizeMismatch {
103                expected: self.block_size,
104                actual,
105                block: self,
106            });
107        }
108        let seq_hash = token_block.kvbm_sequence_hash();
109        self.stage(seq_hash, actual)
110    }
111}
112
113impl<T: BlockMetadata> Drop for MutableBlock<T> {
114    #[inline]
115    fn drop(&mut self) {
116        if self.armed {
117            self.store.release_mutable(self.block_id);
118        }
119    }
120}
121
122impl<T: BlockMetadata> std::fmt::Debug for MutableBlock<T> {
123    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124        f.debug_struct("MutableBlock")
125            .field("block_id", &self.block_id)
126            .finish()
127    }
128}