Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
fominok committed Nov 4, 2024
1 parent 4af11cf commit 3fd7002
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 0 deletions.
18 changes: 18 additions & 0 deletions grovedb/2024-11-04.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Journal

tags: #journal/grovedb

## 10:35

A new merk cache shall be done.

[ ] Implement a solution for keeping Merks in memory and an ability to get multiple mutable links

# Capture

## 13:52

Some things I forgot to do:

[ ] Fix kitchen lower part
[ ] Fix Nadja's headphones
2 changes: 2 additions & 0 deletions grovedb/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ pub mod element;
pub mod error;
#[cfg(feature = "estimated_costs")]
mod estimated_costs;
#[cfg(feature = "full")]
mod merk_cache;
#[cfg(any(feature = "full", feature = "verify"))]
pub mod operations;
#[cfg(any(feature = "full", feature = "verify"))]
Expand Down
106 changes: 106 additions & 0 deletions grovedb/src/merk_cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
//! Module dedicated to keep necessary Merks in memory and solve propagation
//! after usage automatically.
use std::{
collections::{hash_map::Entry, HashMap, HashSet},
mem::{self, MaybeUninit},
};

use grovedb_costs::{cost_return_on_error, CostResult, CostsExt};
use grovedb_merk::Merk;
use grovedb_path::{SubtreePath, SubtreePathBuilder};

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

View workflow job for this annotation

GitHub Actions / clippy

unused import: `SubtreePathBuilder`

warning: unused import: `SubtreePathBuilder` --> grovedb/src/merk_cache.rs:11:33 | 11 | use grovedb_path::{SubtreePath, SubtreePathBuilder}; | ^^^^^^^^^^^^^^^^^^ | = note: `#[warn(unused_imports)]` on by default
use grovedb_storage::{
rocksdb_storage::{PrefixedRocksDbTransactionContext, RocksDbStorage},

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

View workflow job for this annotation

GitHub Actions / clippy

unused import: `RocksDbStorage`

warning: unused import: `RocksDbStorage` --> grovedb/src/merk_cache.rs:13:58 | 13 | rocksdb_storage::{PrefixedRocksDbTransactionContext, RocksDbStorage}, | ^^^^^^^^^^^^^^
StorageBatch,
};
use grovedb_version::version::GroveVersion;

use crate::{Error, GroveDb, Transaction};

type TxMerk<'db> = Merk<PrefixedRocksDbTransactionContext<'db>>;

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

View workflow job for this annotation

GitHub Actions / clippy

type alias `TxMerk` is never used

warning: type alias `TxMerk` is never used --> grovedb/src/merk_cache.rs:20:6 | 20 | type TxMerk<'db> = Merk<PrefixedRocksDbTransactionContext<'db>>; | ^^^^^^

/// Merk caching structure.
///
/// Since we usually postpone all writes to the very end with a single RocksDB
/// batch all intermediate changes to subtrees might not be tracked if we reopen
/// 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.
pub(crate) struct MerkCache<'db, 'b, B> {

Check warning on line 29 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:29:19 | 29 | pub(crate) struct MerkCache<'db, 'b, B> { | ^^^^^^^^^
db: &'db GroveDb,
tx: &'db Transaction<'db>,
batch: &'db StorageBatch,
version: &'db GroveVersion,
inner: HashMap<SubtreePath<'b, B>, TxMerk<'db>>,
}
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.
pub(crate) fn get_merk_mut<'s>(

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

View workflow job for this annotation

GitHub Actions / clippy

methods `get_merk_mut` and `get_multi_mut` are never used

warning: methods `get_merk_mut` and `get_multi_mut` are never used --> grovedb/src/merk_cache.rs:40:19 | 36 | impl<'db, 'b, B: AsRef<[u8]>> MerkCache<'db, 'b, B> { | --------------------------------------------------- methods in this implementation ... 40 | pub(crate) fn get_merk_mut<'s>( | ^^^^^^^^^^^^ ... 70 | pub(crate) fn get_multi_mut<'s, const N: usize>( | ^^^^^^^^^^^^^
&'s mut self,
path: SubtreePath<'b, B>,
) -> CostResult<&'s mut TxMerk<'db>, Error> {
let mut cost = Default::default();

match self.inner.entry(path) {
Entry::Occupied(mut e) => Ok(e.into_mut()).wrap_with_cost(cost),

Check warning on line 47 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:47:29 | 47 | Entry::Occupied(mut e) => Ok(e.into_mut()).wrap_with_cost(cost), | ----^ | | | help: remove this `mut` | = note: `#[warn(unused_mut)]` on by default
Entry::Vacant(e) => {
let merk = cost_return_on_error!(
&mut cost,
self.db.open_transactional_merk_at_path(
e.key().clone(),
self.tx,
Some(self.batch),
self.version
)
);
Ok(e.insert(merk)).wrap_with_cost(cost)
}
}
}

/// Returns an array of mutable references to different Merks, where each
/// element in the array corresponds to a unique Merk based on its
/// position in the input paths array.
///
/// # 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,
paths: [SubtreePath<'b, B>; N],
) -> CostResult<[&'s mut TxMerk<'db>; N], Error> {
let mut result_uninit = [const { MaybeUninit::<&mut TxMerk>::uninit() }; N];
let mut cost = Default::default();

let unique_args: HashSet<_> = paths.iter().collect();
if unique_args.len() != N {
panic!("`get_multi_mut` keys must be unique");
}

for (i, path) in paths.into_iter().enumerate() {
// SAFETY is ensured by tying the lifetime of mutable references to the
// collection itself, preventing them from outliving the collection and
// ensuring exclusive access to the collection's layout through other
// mutable references. The mandatory keys' uniqueness check above makes
// sure no overlapping memory will be referenced.
let merk_ref = unsafe {
(cost_return_on_error!(&mut cost, self.get_merk_mut(path)) as *mut TxMerk<'db>)
.as_mut::<'s>()
.expect("not a null pointer")
};
result_uninit[i].write(merk_ref);
}

// SAFETY: An array of `MaybeUninit` references takes the same size as an array
// of references as long as they both have the same number of elements,
// 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 [&mut TxMerk; N]).read() };
mem::forget(result_uninit);

Check warning on line 102 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:102:9 | 102 | mem::forget(result_uninit); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: argument has type `[std::mem::MaybeUninit<&mut grovedb_merk::Merk<grovedb_storage::rocksdb_storage::PrefixedRocksDbTransactionContext<'_>>>; N]` --> grovedb/src/merk_cache.rs:102:21 | 102 | 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)
}
}

0 comments on commit 3fd7002

Please sign in to comment.