Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
fominok committed Nov 6, 2024
1 parent ea61c39 commit 2f134ac
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 39 deletions.
140 changes: 106 additions & 34 deletions grovedb/src/merk_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::{Element, Error, GroveDb, Transaction};
type TxMerk<'db> = Merk<PrefixedRocksDbTransactionContext<'db>>;

struct CachedMerk<'db> {

Check warning on line 20 in grovedb/src/merk_cache.rs

View workflow job for this annotation

GitHub Actions / clippy

struct `CachedMerk` is never constructed

warning: struct `CachedMerk` is never constructed --> grovedb/src/merk_cache.rs:20:8 | 20 | struct CachedMerk<'db> { | ^^^^^^^^^^
updated: bool,
to_propagate: bool,
merk: TxMerk<'db>,
}

Expand All @@ -29,6 +29,7 @@ struct CachedMerk<'db> {
/// those Merks, so it's better to have them cached and proceed through the same
/// structure. Eventually we'll have enough info at the same place to perform
/// necessary propagations as well.
// SAFETY: please consult with other safety docs here before doing any changes
pub(crate) struct MerkCache<'db, 'b, B> {

Check warning on line 33 in grovedb/src/merk_cache.rs

View workflow job for this annotation

GitHub Actions / clippy

struct `MerkCache` is never constructed

warning: struct `MerkCache` is never constructed --> grovedb/src/merk_cache.rs:33:19 | 33 | pub(crate) struct MerkCache<'db, 'b, B> { | ^^^^^^^^^
merks: BTreeMap<SubtreePath<'b, B>, CachedMerk<'db>>,
db: &'db GroveDb,
Expand All @@ -38,14 +39,14 @@ pub(crate) struct MerkCache<'db, 'b, B> {
}

impl<'db, 'b, B: AsRef<[u8]>> MerkCache<'db, 'b, B> {
pub(crate) fn new(
pub(crate) fn new<'tx>(

Check warning on line 42 in grovedb/src/merk_cache.rs

View workflow job for this annotation

GitHub Actions / clippy

this lifetime isn't used in the function definition

warning: this lifetime isn't used in the function definition --> grovedb/src/merk_cache.rs:42:23 | 42 | pub(crate) fn new<'tx>( | ^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#extra_unused_lifetimes = note: `#[warn(clippy::extra_unused_lifetimes)]` on by default

Check warning on line 42 in grovedb/src/merk_cache.rs

View workflow job for this annotation

GitHub Actions / clippy

associated items `new`, `get_merk_mut_internal`, `get_multi_mut`, `finalize`, and `propagate_updated_merks` are never used

warning: associated items `new`, `get_merk_mut_internal`, `get_multi_mut`, `finalize`, and `propagate_updated_merks` are never used --> grovedb/src/merk_cache.rs:42:19 | 41 | impl<'db, 'b, B: AsRef<[u8]>> MerkCache<'db, 'b, B> { | --------------------------------------------------- associated items in this implementation 42 | pub(crate) fn new<'tx>( | ^^^ ... 59 | fn get_merk_mut_internal<'c>( | ^^^^^^^^^^^^^^^^^^^^^ ... 93 | pub(crate) fn get_multi_mut<'c, const N: usize>( | ^^^^^^^^^^^^^ ... 136 | pub(crate) fn finalize(mut self) -> CostResult<Box<StorageBatch>, Error> { | ^^^^^^^^ ... 155 | fn propagate_updated_merks(mut self) -> CostResult<(), Error> { | ^^^^^^^^^^^^^^^^^^^^^^^
db: &'db GroveDb,
tx: &'db Transaction<'db>,
version: &'db GroveVersion,
) -> Self {
MerkCache {
db,
tx,
tx: &tx,

Check warning on line 49 in grovedb/src/merk_cache.rs

View workflow job for this annotation

GitHub Actions / clippy

this expression creates a reference which is immediately dereferenced by the compiler

warning: this expression creates a reference which is immediately dereferenced by the compiler --> grovedb/src/merk_cache.rs:49:17 | 49 | tx: &tx, | ^^^ help: change this to: `tx` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow
version,
batch: Box::leak(Box::new(StorageBatch::default())),
merks: Default::default(),
Expand All @@ -55,10 +56,10 @@ impl<'db, 'b, B: AsRef<[u8]>> MerkCache<'db, 'b, B> {
/// Get a mutable Merk reference from the cache.
/// If it doesn't present then it will be opened.
/// Returns `None` if there is no Merk under this path.
fn get_merk_mut_internal<'s>(
&'s mut self,
fn get_merk_mut_internal<'c>(
&'c mut self,
path: SubtreePath<'b, B>,
) -> CostResult<&'s mut CachedMerk<'db>, Error> {
) -> CostResult<&'c mut CachedMerk<'db>, Error> {
let mut cost = Default::default();

match self.merks.entry(path) {
Expand All @@ -75,7 +76,7 @@ impl<'db, 'b, B: AsRef<[u8]>> MerkCache<'db, 'b, B> {
);
Ok(e.insert(CachedMerk {
merk,
updated: false,
to_propagate: false,
}))
.wrap_with_cost(cost)
}
Expand All @@ -89,11 +90,11 @@ impl<'db, 'b, B: AsRef<[u8]>> MerkCache<'db, 'b, B> {
/// # Panics
/// All input paths *must* be unique, otherwise it could provide multiple
/// mutable references to the same memory which is strictly prohibited.
pub(crate) fn get_multi_mut<'s, const N: usize>(
&'s mut self,
pub(crate) fn get_multi_mut<'c, const N: usize>(
&'c mut self,
paths: [SubtreePath<'b, B>; N],
) -> CostResult<[MerkHandle<'db, 's, 'b, B>; N], Error> {
let mut result_uninit = [const { MaybeUninit::<MerkHandle<'db, 's, 'b, B>>::uninit() }; N];
) -> CostResult<[MerkHandle<'db, 'c>; N], Error> {
let mut result_uninit = [const { MaybeUninit::<MerkHandle<'db, 'c>>::uninit() }; N];
let mut cost = Default::default();

let unique_args: HashSet<_> = paths.iter().collect();
Expand All @@ -109,10 +110,9 @@ impl<'db, 'b, B: AsRef<[u8]>> MerkCache<'db, 'b, B> {
// sure no overlapping memory will be referenced.
let merk_ref = unsafe {
MerkHandle::new(
path.clone(),
(cost_return_on_error!(&mut cost, self.get_merk_mut_internal(path))
as *mut CachedMerk<'db>)
.as_mut::<'s>()
.as_mut::<'c>()
.expect("not a null pointer"),
&self.version,

Check warning on line 117 in grovedb/src/merk_cache.rs

View workflow job for this annotation

GitHub Actions / clippy

this expression creates a reference which is immediately dereferenced by the compiler

warning: this expression creates a reference which is immediately dereferenced by the compiler --> grovedb/src/merk_cache.rs:117:21 | 117 | &self.version, | ^^^^^^^^^^^^^ help: change this to: `self.version` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow
)
Expand All @@ -125,45 +125,92 @@ impl<'db, 'b, B: AsRef<[u8]>> MerkCache<'db, 'b, B> {
// N in our case. `mem::transmute` would represent it better, however,
// due to poor support of const generics in stable Rust we bypass
// compile-time size checks with pointer casts.
let result = unsafe { (&result_uninit as *const _ as *const [MerkHandle<B>; N]).read() };
let result = unsafe { (&result_uninit as *const _ as *const [MerkHandle; N]).read() };
mem::forget(result_uninit);

Check warning on line 129 in grovedb/src/merk_cache.rs

View workflow job for this annotation

GitHub Actions / clippy

call to `std::mem::forget` with a value that does not implement `Drop`. Forgetting such a type is the same as dropping it

warning: call to `std::mem::forget` with a value that does not implement `Drop`. Forgetting such a type is the same as dropping it --> grovedb/src/merk_cache.rs:129:9 | 129 | mem::forget(result_uninit); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: argument has type `[std::mem::MaybeUninit<merk_cache::MerkHandle<'_, '_>>; N]` --> grovedb/src/merk_cache.rs:129:21 | 129 | mem::forget(result_uninit); | ^^^^^^^^^^^^^ = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#forget_non_drop = note: `#[warn(clippy::forget_non_drop)]` on by default

Ok(result).wrap_with_cost(cost)
}

/// Summarizes all performed operations on this `MerkCache` with necessary
/// propagations into a `Storagebatch`.
pub(crate) fn finalize(self) -> Box<StorageBatch> {
pub(crate) fn finalize(mut self) -> CostResult<Box<StorageBatch>, Error> {

Check warning on line 136 in grovedb/src/merk_cache.rs

View workflow job for this annotation

GitHub Actions / clippy

variable does not need to be mutable

warning: variable does not need to be mutable --> grovedb/src/merk_cache.rs:136:28 | 136 | pub(crate) fn finalize(mut self) -> CostResult<Box<StorageBatch>, Error> { | ----^^^^ | | | help: remove this `mut` | = note: `#[warn(unused_mut)]` on by default
let batch_ptr = self.batch as *const _;
// Remove all possible usages of the `StorageBatch`:
drop(self);

// Propagate updated subtrees' hashes up to the root and dropping all possible
// batch users:
let propagation_result = self.propagate_updated_merks();

// SAFETY: The batch reference was created by `Box::leak` and restored into a
// `Box` form. No shared usage of that memory exists because the
// `MerkCache` structure was dropped above.
unsafe { Box::from_raw(batch_ptr as *mut _) }
let result_batch = unsafe { Box::from_raw(batch_ptr as *mut _) };

// The batch's unsafety cleanup happens regardless of propagation results, so no
// early termination allowed. We do the mapping afterwards:
propagation_result.map_ok(|_| result_batch)
}

/// Finalizes each Merk starting from the deepest subtrees, updating hashes
/// up to the root.
fn propagate_updated_merks(mut self) -> CostResult<(), Error> {
let mut cost = Default::default();

let version: &'db GroveVersion = self.version;

// Picking Merks one by one as long as they have a parent
while let Some((parent_path, parent_key, subtree)) =
self.merks.pop_first().and_then(|(path, subtree)| {
path.derive_parent()
.map(|(parent_path, parent_key)| (parent_path, parent_key, subtree))
})
{
// If a cached Merk wasn't changed, we don't need to propagate it:
if !subtree.to_propagate {
continue;
}

let parent_subtree =
cost_return_on_error!(&mut cost, self.get_merk_mut_internal(parent_path));
parent_subtree.to_propagate = true;
let (root_hash, root_key, root_sum) = cost_return_on_error!(
&mut cost,
subtree.merk.root_hash_key_and_sum().map_err(|e| e.into())
);
cost_return_on_error!(
&mut cost,
GroveDb::update_tree_item_preserve_flag(
&mut parent_subtree.merk,
parent_key,
root_key,
root_hash,
root_sum,
version
)
);
}

Ok(()).wrap_with_cost(cost)
}
}

/// Handle to a cached Merk.
pub(crate) struct MerkHandle<'db, 'c, 'b, B> {
path: SubtreePath<'b, B>,
pub(crate) struct MerkHandle<'db, 'c> {

Check warning on line 197 in grovedb/src/merk_cache.rs

View workflow job for this annotation

GitHub Actions / clippy

struct `MerkHandle` is never constructed

warning: struct `MerkHandle` is never constructed --> grovedb/src/merk_cache.rs:197:19 | 197 | pub(crate) struct MerkHandle<'db, 'c> { | ^^^^^^^^^^
merk: &'c mut TxMerk<'db>,
version: &'db GroveVersion,
updated: &'c mut bool,
to_propagate: &'c mut bool,
}

/// It is allowed to dereference `MerkHandle` to regular Merks but in a
/// non-mutable way since we want to track what have been done to those Merks.
impl<'db, 'c, 'b, B> Deref for MerkHandle<'db, 'c, 'b, B> {
impl<'db, 'c> Deref for MerkHandle<'db, 'c> {
type Target = TxMerk<'db>;

fn deref(&self) -> &Self::Target {
&self.merk

Check warning on line 209 in grovedb/src/merk_cache.rs

View workflow job for this annotation

GitHub Actions / clippy

this expression creates a reference which is immediately dereferenced by the compiler

warning: this expression creates a reference which is immediately dereferenced by the compiler --> grovedb/src/merk_cache.rs:209:9 | 209 | &self.merk | ^^^^^^^^^^ help: change this to: `self.merk` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow
}
}

impl<'db, 'c, 'b, B> MerkHandle<'db, 'c, 'b, B> {
impl<'db, 'c> MerkHandle<'db, 'c> {
pub(crate) fn insert(

Check warning on line 214 in grovedb/src/merk_cache.rs

View workflow job for this annotation

GitHub Actions / clippy

associated items `insert` and `new` are never used

warning: associated items `insert` and `new` are never used --> grovedb/src/merk_cache.rs:214:19 | 213 | impl<'db, 'c> MerkHandle<'db, 'c> { | --------------------------------- associated items in this implementation 214 | pub(crate) fn insert( | ^^^^^^ ... 225 | fn new(cached_merk: &'c mut CachedMerk<'db>, version: &'db GroveVersion) -> Self { | ^^^
&mut self,
key: impl AsRef<[u8]>,
Expand All @@ -172,19 +219,14 @@ impl<'db, 'c, 'b, B> MerkHandle<'db, 'c, 'b, B> {
) -> CostResult<(), Error> {
element
.insert(self.merk, key, options, self.version)
.for_ok(|_| *self.updated = true)
.for_ok(|_| *self.to_propagate = true)
}

fn new(
path: SubtreePath<'b, B>,
cached_merk: &'c mut CachedMerk<'db>,
version: &'db GroveVersion,
) -> Self {
fn new(cached_merk: &'c mut CachedMerk<'db>, version: &'db GroveVersion) -> Self {
Self {
path,
merk: &mut cached_merk.merk,
version,
updated: &mut cached_merk.updated,
to_propagate: &mut cached_merk.to_propagate,
}
}
}
Expand All @@ -197,14 +239,16 @@ mod tests {
use grovedb_version::version::GroveVersion;

use super::MerkCache;
use crate::tests::{make_deep_tree, ANOTHER_TEST_LEAF, TEST_LEAF};
use crate::{
tests::{make_deep_tree, ANOTHER_TEST_LEAF, TEST_LEAF},
Element,
};

#[test]
fn cached_subtrees_are_free() {
let version = GroveVersion::latest();
let db = make_deep_tree(&version);
let tx = db.start_transaction();
let batch = StorageBatch::new();
let mut cache = MerkCache::new(&db, &tx, version);

let mut cost: OperationCost = Default::default();
Expand Down Expand Up @@ -242,12 +286,40 @@ mod tests {
let version = GroveVersion::latest();
let db = make_deep_tree(&version);
let tx = db.start_transaction();
let batch = StorageBatch::new();
let mut cache = MerkCache::new(&db, &tx, version);

let _ = cache.get_multi_mut([
SubtreePath::from(&[TEST_LEAF]),
SubtreePath::from(&[TEST_LEAF]),
]);
}

#[test]
fn changes_to_merk_cache_provide_non_empty_batch() {
let version = GroveVersion::latest();
let db = make_deep_tree(&version);
let tx = db.start_transaction();

let mut cache = MerkCache::new(&db, &tx, version);
let [_subtree] = cache
.get_multi_mut([SubtreePath::from(&[TEST_LEAF])])
.unwrap()
.unwrap();

// Do nothing and finalize a batch:
assert!(cache.finalize().unwrap().unwrap().len() == 0);

let mut cache = MerkCache::new(&db, &tx, version);
let [mut subtree] = cache
.get_multi_mut([SubtreePath::from(&[TEST_LEAF])])
.unwrap()
.unwrap();
subtree
.insert(b"ayy", Element::new_item(b"lmao".to_vec()), None)
.unwrap()
.unwrap();

// Do something and finalize another batch:
assert!(cache.finalize().unwrap().unwrap().len() > 0);
}
}
7 changes: 4 additions & 3 deletions path/src/subtree_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ where
iter_a
.len()
.cmp(&iter_b.len())
.reverse()
.then_with(|| iter_a.cmp(iter_b)),
)
}
Expand Down Expand Up @@ -340,8 +341,8 @@ mod tests {
cmp::Ordering::Equal
));

// Longer paths are "greater than":
assert!(path_a < path_b);
assert!(path_a < path_c);
// Longer paths come first
assert!(path_a > path_b);
assert!(path_a > path_c);
}
}
4 changes: 2 additions & 2 deletions storage/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,8 +305,8 @@ impl StorageBatch {
}
}

#[cfg(test)]
pub(crate) fn len(&self) -> usize {
/// Returns a number of operations in the batch.
pub fn len(&self) -> usize {

Check warning on line 309 in storage/src/storage.rs

View workflow job for this annotation

GitHub Actions / clippy

struct `StorageBatch` has a public `len` method, but no `is_empty` method

warning: struct `StorageBatch` has a public `len` method, but no `is_empty` method --> storage/src/storage.rs:309:5 | 309 | pub fn len(&self) -> usize { | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#len_without_is_empty = note: `#[warn(clippy::len_without_is_empty)]` on by default
let operations = self.operations.borrow();
operations.data.len()
+ operations.roots.len()
Expand Down

0 comments on commit 2f134ac

Please sign in to comment.