Skip to content

Commit

Permalink
Remove TypedView trait
Browse files Browse the repository at this point in the history
This trait was only implemented by StoredView and was always wrapped in
a dyn. This removes the vtable.

The other change here requires that items that are Storable are Send,
Sync, and Debug, which greatly simplifies the trait bounds.
  • Loading branch information
rkuris committed Nov 1, 2023
1 parent 19edacf commit 766889a
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 49 deletions.
8 changes: 4 additions & 4 deletions shale/src/compact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ impl<M: CachedStore> CompactSpaceInner<M> {
StoredView::ptr_to_obj(self.meta_space.as_ref(), ptr, CompactDescriptor::MSIZE)
}

fn get_data_ref<U: Storable + Debug + Send + Sync + 'static>(
fn get_data_ref<U: Storable + 'static>(
&self,
ptr: DiskAddress,
len_limit: u64,
Expand Down Expand Up @@ -493,12 +493,12 @@ impl<M: CachedStore> CompactSpaceInner<M> {
}

#[derive(Debug)]
pub struct CompactSpace<T: Send + Sync, M> {
pub struct CompactSpace<T: Storable, M> {
inner: RwLock<CompactSpaceInner<M>>,
obj_cache: ObjCache<T>,
}

impl<T: Storable + Send + Sync, M: CachedStore> CompactSpace<T, M> {
impl<T: Storable, M: CachedStore> CompactSpace<T, M> {
pub fn new(
meta_space: Arc<M>,
compact_space: Arc<M>,
Expand All @@ -521,7 +521,7 @@ impl<T: Storable + Send + Sync, M: CachedStore> CompactSpace<T, M> {
}
}

impl<T: Storable + Send + Sync + Debug + 'static, M: CachedStore + Send + Sync> ShaleStore<T>
impl<T: Storable + 'static, M: CachedStore + Send + Sync> ShaleStore<T>
for CompactSpace<T, M>
{
fn put_item(&self, item: T, extra: u64) -> Result<ObjRef<'_, T>, ShaleError> {
Expand Down
65 changes: 20 additions & 45 deletions shale/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,43 +93,18 @@ pub trait CachedStore: Debug + Send + Sync {
fn id(&self) -> SpaceId;
}

/// A addressed, typed, and read-writable handle for the stored item in [ShaleStore]. The object
/// represents the decoded/mapped data. The implementation of [ShaleStore] could use [ObjCache] to
/// turn a `TypedView` into an [ObjRef].
pub trait TypedView<T: ?Sized + Send + Sync>:
std::fmt::Debug + Deref<Target = T> + Send + Sync
{
/// Get the offset of the initial byte in the linear space.
fn get_offset(&self) -> usize;
/// Access it as a [CachedStore] object.
fn get_mem_store(&self) -> &dyn CachedStore;
/// Access it as a mutable CachedStore object
fn get_mut_mem_store(&mut self) -> &mut dyn CachedStore;
/// Estimate the serialized length of the current type content. It should not be smaller than
/// the actually length.
fn estimate_mem_image(&self) -> Option<u64>;
/// Serialize the type content to the memory image. It defines how the current in-memory object
/// of `T` should be represented in the linear storage space.
fn write_mem_image(&self, mem_image: &mut [u8]) -> Result<(), ShaleError>;
/// Gain mutable access to the typed content. By changing it, its serialized bytes (and length)
/// could change.
fn write(&mut self) -> &mut T;
/// Returns if the typed content is memory-mapped (i.e., all changes through `write` are auto
/// reflected in the underlying [CachedStore]).
fn is_mem_mapped(&self) -> bool;
}

/// A wrapper of `TypedView` to enable writes. The direct construction (by [Obj::from_typed_view]
/// or [StoredView::ptr_to_obj]) could be useful for some unsafe access to a low-level item (e.g.
/// headers/metadata at bootstrap or part of [ShaleStore] implementation) stored at a given [DiskAddress]
/// . Users of [ShaleStore] implementation, however, will only use [ObjRef] for safeguarded access.
#[derive(Debug)]
pub struct Obj<T: ?Sized + Send + Sync> {
value: Box<dyn TypedView<T>>,
pub struct Obj<T: Storable> {
value: Box<StoredView<T>>,
dirty: Option<u64>,
}

impl<T: ?Sized + Send + Sync> Obj<T> {
impl<T: Storable> Obj<T> {
#[inline(always)]
pub fn as_ptr(&self) -> DiskAddress {
DiskAddress(NonZeroUsize::new(self.value.get_offset()))
Expand All @@ -155,7 +130,7 @@ impl<T: ?Sized + Send + Sync> Obj<T> {
}

#[inline(always)]
pub fn from_typed_view(value: Box<dyn TypedView<T>>) -> Self {
pub fn from_typed_view(value: Box<StoredView<T>>) -> Self {
Obj { value, dirty: None }
}

Expand All @@ -173,26 +148,26 @@ impl<T: ?Sized + Send + Sync> Obj<T> {
}
}

impl<T: ?Sized + Send + Sync> Drop for Obj<T> {
impl<T: Storable> Drop for Obj<T> {
fn drop(&mut self) {
self.flush_dirty()
}
}

impl<T: ?Sized + Send + Sync> Deref for Obj<T> {
impl<T: Storable> Deref for Obj<T> {
type Target = T;
fn deref(&self) -> &T {
&self.value
}
}

/// User handle that offers read & write access to the stored [ShaleStore] item.
pub struct ObjRef<'a, T: Send + Sync> {
pub struct ObjRef<'a, T: Storable> {
inner: Option<Obj<T>>,
cache: &'a ObjCache<T>,
}

impl<'a, T: Send + Sync> ObjRef<'a, T> {
impl<'a, T: Storable> ObjRef<'a, T> {
fn new(inner: Option<Obj<T>>, cache: &'a ObjCache<T>) -> Self {
Self { inner, cache }
}
Expand All @@ -212,15 +187,15 @@ impl<'a, T: Send + Sync> ObjRef<'a, T> {
}
}

impl<'a, T: Send + Sync> Deref for ObjRef<'a, T> {
impl<'a, T: Storable> Deref for ObjRef<'a, T> {
type Target = Obj<T>;
fn deref(&self) -> &Obj<T> {
// TODO: Something is seriously wrong here but I'm not quite sure about the best approach for the fix
self.inner.as_ref().unwrap()
}
}

impl<'a, T: Send + Sync> Drop for ObjRef<'a, T> {
impl<'a, T: Storable> Drop for ObjRef<'a, T> {
fn drop(&mut self) {
let mut inner = self.inner.take().unwrap();
let ptr = inner.as_ptr();
Expand All @@ -238,7 +213,7 @@ impl<'a, T: Send + Sync> Drop for ObjRef<'a, T> {

/// A persistent item storage backed by linear logical space. New items can be created and old
/// items could be retrieved or dropped.
pub trait ShaleStore<T: Send + Sync> {
pub trait ShaleStore<T: Storable> {
/// Dereference [DiskAddress] to a unique handle that allows direct access to the item in memory.
fn get_item(&'_ self, ptr: DiskAddress) -> Result<ObjRef<'_, T>, ShaleError>;
/// Allocate a new item.
Expand All @@ -252,7 +227,7 @@ pub trait ShaleStore<T: Send + Sync> {
/// A stored item type that can be decoded from or encoded to on-disk raw bytes. An efficient
/// implementation could be directly transmuting to/from a POD struct. But sometimes necessary
/// compression/decompression is needed to reduce disk I/O and facilitate faster in-memory access.
pub trait Storable {
pub trait Storable: Debug + Send + Sync {
fn dehydrated_len(&self) -> u64;
fn dehydrate(&self, to: &mut [u8]) -> Result<(), ShaleError>;
fn hydrate<T: CachedStore>(addr: usize, mem: &T) -> Result<Self, ShaleError>
Expand All @@ -277,7 +252,7 @@ pub struct StoredView<T> {
len_limit: u64,
}

impl<T: Storable + Debug> Debug for StoredView<T> {
impl<T: Debug> Debug for StoredView<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let StoredView {
decoded,
Expand All @@ -300,7 +275,7 @@ impl<T: Storable> Deref for StoredView<T> {
}
}

impl<T: Storable + Debug + Send + Sync> TypedView<T> for StoredView<T> {
impl<T: Storable> StoredView<T> {
fn get_offset(&self) -> usize {
self.offset
}
Expand Down Expand Up @@ -334,7 +309,7 @@ impl<T: Storable + Debug + Send + Sync> TypedView<T> for StoredView<T> {
}
}

impl<T: Storable + Debug + Send + Sync + 'static> StoredView<T> {
impl<T: Storable + 'static> StoredView<T> {
#[inline(always)]
fn new<U: CachedStore>(offset: usize, len_limit: u64, space: &U) -> Result<Self, ShaleError> {
let decoded = T::hydrate(offset, space)?;
Expand Down Expand Up @@ -387,7 +362,7 @@ impl<T: Storable + Debug + Send + Sync + 'static> StoredView<T> {
}
}

impl<T: Storable + Send + Sync> StoredView<T> {
impl<T: Storable> StoredView<T> {
fn new_from_slice(
offset: usize,
len_limit: u64,
Expand All @@ -402,7 +377,7 @@ impl<T: Storable + Send + Sync> StoredView<T> {
})
}

pub fn slice<U: Storable + Debug + Send + Sync + 'static>(
pub fn slice<U: Storable + 'static>(
s: &Obj<T>,
offset: usize,
length: u64,
Expand Down Expand Up @@ -430,17 +405,17 @@ impl<T: Storable + Send + Sync> StoredView<T> {
}

#[derive(Debug)]
pub struct ObjCacheInner<T: Send + Sync> {
pub struct ObjCacheInner<T: Storable> {
cached: lru::LruCache<DiskAddress, Obj<T>>,
pinned: HashMap<DiskAddress, bool>,
dirty: HashSet<DiskAddress>,
}

/// [ObjRef] pool that is used by [ShaleStore] implementation to construct [ObjRef]s.
#[derive(Debug)]
pub struct ObjCache<T: Send + Sync>(Arc<RwLock<ObjCacheInner<T>>>);
pub struct ObjCache<T: Storable>(Arc<RwLock<ObjCacheInner<T>>>);

impl<T: Send + Sync> ObjCache<T> {
impl<T: Storable> ObjCache<T> {
pub fn new(capacity: usize) -> Self {
Self(Arc::new(RwLock::new(ObjCacheInner {
cached: lru::LruCache::new(NonZeroUsize::new(capacity).expect("non-zero cache size")),
Expand Down

0 comments on commit 766889a

Please sign in to comment.