-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
- Loading branch information
There are no files selected for viewing
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 |
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 GitHub Actions / clippyunused import: `SubtreePathBuilder`
|
||
use grovedb_storage::{ | ||
rocksdb_storage::{PrefixedRocksDbTransactionContext, RocksDbStorage}, | ||
Check warning on line 13 in grovedb/src/merk_cache.rs GitHub Actions / clippyunused import: `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 GitHub Actions / clippytype alias `TxMerk` is never used
|
||
|
||
/// 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 GitHub Actions / clippystruct `MerkCache` is never constructed
|
||
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 GitHub Actions / clippymethods `get_merk_mut` and `get_multi_mut` are never used
|
||
&'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 GitHub Actions / clippyvariable does not need to be mutable
|
||
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 GitHub Actions / clippycall to `std::mem::forget` with a value that does not implement `Drop`. Forgetting such a type is the same as dropping it
|
||
|
||
Ok(result).wrap_with_cost(cost) | ||
} | ||
} |