From 7ea890765deb854d79e12a60950cb1bde17b88b5 Mon Sep 17 00:00:00 2001 From: Sayan Nandan Date: Sat, 3 Jul 2021 11:32:53 +0530 Subject: [PATCH] Correct kvengine defs and add custom `Lazy` type --- server/src/coredb/lazy.rs | 174 ++++++++++++++++++++++++++++++++++ server/src/coredb/memstore.rs | 55 +++++++---- server/src/coredb/mod.rs | 3 +- server/src/kvengine/mod.rs | 1 + server/src/util.rs | 17 ++++ 5 files changed, 233 insertions(+), 17 deletions(-) create mode 100644 server/src/coredb/lazy.rs diff --git a/server/src/coredb/lazy.rs b/server/src/coredb/lazy.rs new file mode 100644 index 00000000..1af2c732 --- /dev/null +++ b/server/src/coredb/lazy.rs @@ -0,0 +1,174 @@ +/* + * Created on Sat Jul 03 2021 + * + * This file is a part of Skytable + * Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source + * NoSQL database written by Sayan Nandan ("the Author") with the + * vision to provide flexibility in data modelling without compromising + * on performance, queryability or scalability. + * + * Copyright (c) 2021, Sayan Nandan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * +*/ + +use core::hint::spin_loop as let_the_cpu_relax; +use core::mem; +use core::ops::Deref; +use core::ptr; +use core::sync::atomic::AtomicBool; +use core::sync::atomic::AtomicPtr; +use core::sync::atomic::Ordering; + +const ORD_ACQ: Ordering = Ordering::Acquire; +const ORD_SEQ: Ordering = Ordering::SeqCst; + +/// A lazily intialized, or _call by need_ value +#[derive(Debug)] +pub struct Lazy { + /// the value (null at first) + value: AtomicPtr, + /// the function that will init the value + init_func: F, + /// is some thread trying to initialize the value + init_state: AtomicBool, +} + +impl Lazy { + pub const fn new(init_func: F) -> Self { + Self { + value: AtomicPtr::new(ptr::null_mut()), + init_func, + init_state: AtomicBool::new(false), + } + } +} + +impl Deref for Lazy +where + F: Fn() -> T, +{ + type Target = T; + fn deref(&self) -> &Self::Target { + let value_ptr = self.value.load(ORD_ACQ); + if !value_ptr.is_null() { + // the value has already been initialized, return + unsafe { + // UNSAFE(@ohsayan): We've just asserted that the value is not null + return &*value_ptr; + } + } + // it's null, so it's useless + + // hold on until someone is trying to init + while self + .init_state + .compare_exchange(false, true, ORD_SEQ, ORD_SEQ) + .is_err() + { + let_the_cpu_relax(); + } + /* + see the value before the last store. while we were one the loop, + some other thread could have initialized it already + */ + let value_ptr = self.value.load(ORD_ACQ); + if !value_ptr.is_null() { + // no more init, someone initialized it already + assert!(self.init_state.swap(false, ORD_SEQ)); + unsafe { + // UNSAFE(@ohsayan): We've already loaded the value checked + // that it isn't null + &*value_ptr + } + } else { + // so no one cared to initialize the value in between + // fine, we'll init it + let value = (self.init_func)(); + let value_ptr = Box::into_raw(Box::new(value)); + // now swap out the older value and check it for sanity + assert!(self.value.swap(value_ptr, ORD_SEQ).is_null()); + // set trying to init flag to false + assert!(self.init_state.swap(false, ORD_SEQ)); + unsafe { + // UNSAFE(@ohsayan): We just initialized the value ourselves + // so it is not null! + &*value_ptr + } + } + } +} + +impl Drop for Lazy { + fn drop(&mut self) { + if mem::needs_drop::() { + // this needs drop + let value_ptr = self.value.load(ORD_ACQ); + if !value_ptr.is_null() { + unsafe { + // UNSAFE(@ohsayan): We've just checked if the value is null or not + mem::drop(Box::from_raw(value_ptr)) + } + } + } + } +} + +cfg_test!( + use crate::coredb::htable::{HTable, Data}; + use crate::coredb::lazy; + use std::thread; + typedef!( + /// A function that returns a [`HTable`] + RetNs = fn() -> HTable; + /// A [`Htable`] typedef + Htable = HTable; + ); + + static LAZY_VALUE: lazy::Lazy = lazy::Lazy::new(|| { + let ht = HTable::new(); + ht.true_if_insert("sayan".into(), "is doing something".into()); + ht + }); + + #[test] + fn test_lazy() { + assert_eq!( + LAZY_VALUE.get("sayan".as_bytes()).unwrap().clone(), + Data::from("is doing something") + ); + } + + #[test] + fn test_two_threads_trying_to_get_at_once() { + let (t1, t2) = ( + thread::spawn(|| { + assert_eq!( + LAZY_VALUE.get("sayan".as_bytes()).unwrap().clone(), + Data::from("is doing something") + );}), + thread::spawn(|| { + assert_eq!( + LAZY_VALUE.get("sayan".as_bytes()).unwrap().clone(), + Data::from("is doing something") + ); + }) + ); + { + t1.join().unwrap(); + t2.join().unwrap(); + } + } +); diff --git a/server/src/coredb/memstore.rs b/server/src/coredb/memstore.rs index 83795fb9..2f9212f7 100644 --- a/server/src/coredb/memstore.rs +++ b/server/src/coredb/memstore.rs @@ -24,11 +24,42 @@ * */ +//! # In-memory store +//! +//! This is what things look like: +//! ```text +//! ------------------------------------------------------ +//! | | | +//! | |-------------------| | |-------------------| | +//! | |-------------------| | |-------------------| | +//! | | | TABLE | TABLE | | | | | TABLE | TABLE | | | +//! | | |-------|-------| | | | |-------|-------| | | +//! | | Keyspace | | | Keyspace | | +//! | |-------------------| | |-------------------| | +//! | | +//! | |-------------------| | |-------------------| | +//! | | |-------|-------| | | | |-------|-------| | | +//! | | | TABLE | TABLE | | | | | TABLE | TABLE | | | +//! | | |-------|-------| | | | |-------|-------| | | +//! | | Keyspace | | | Keyspace | | +//! | |-------------------| | |-------------------| | +//! | | | +//! | | | +//! | NAMESPACE | NAMESPACE | +//! ------------------------------------------------------ +//! | NODE | +//! |----------------------------------------------------| +//! ``` +//! +//! So, all your data is at the mercy of [`Memstore`]'s constructor +//! and destructor. + #![allow(dead_code)] // TODO(@ohsayan): Remove this onece we're done use crate::coredb::htable::Data; use crate::coredb::htable::HTable; use crate::coredb::SnapshotStatus; +use crate::kvengine::KVEngine; use std::sync::atomic::AtomicBool; use std::sync::Arc; @@ -63,8 +94,8 @@ impl Default for ReplicationStrategy { /// This in-memory table that houses all keyspaces and namespaces along with other node /// properties pub struct Memstore { - /// the namespaces - namespace: HTable>, + /// the keyspaces + keyspaces: HTable>, /// the shard range shard_range: ClusterShardRange, } @@ -72,10 +103,10 @@ pub struct Memstore { // TODO(@ohsayan): Optimize the memory layouts of the UDFs to ensure that sharing is very cheap #[derive(Debug)] -/// The namespace that houses all the other tables -pub struct Namespace { +/// The keyspace that houses all the other tables +pub struct Keyspace { /// the tables - tables: HTable>, + tables: HTable>, /// current state of the disk flush status. if this is true, we're safe to /// go ahead with writes flush_state_healthy: AtomicBool, @@ -88,16 +119,8 @@ pub struct Namespace { // same 8 byte ptrs; any chance of optimizations? #[derive(Debug)] -/// The underlying keyspace type. This is the place for the other data models (soon!) -pub enum Keyspace { +/// The underlying table type. This is the place for the other data models (soon!) +pub enum Table { /// a key/value store - KV(KVStore), -} - -#[derive(Debug)] -/// The keyspace that houses atomic references to the actual key value pairs. Again, no one -/// owns anything: just pointers -pub struct KVStore { - /// the inner table - table: HTable, + KV(KVEngine), } diff --git a/server/src/coredb/mod.rs b/server/src/coredb/mod.rs index 61c5021f..0819f957 100644 --- a/server/src/coredb/mod.rs +++ b/server/src/coredb/mod.rs @@ -39,7 +39,8 @@ pub use htable::Data; use libsky::TResult; use std::sync::Arc; pub mod htable; -mod lock; +pub mod lazy; +pub mod lock; mod memstore; /// This is a thread-safe database handle, which on cloning simply diff --git a/server/src/kvengine/mod.rs b/server/src/kvengine/mod.rs index 86cc6851..2313d439 100644 --- a/server/src/kvengine/mod.rs +++ b/server/src/kvengine/mod.rs @@ -72,6 +72,7 @@ impl<'a> ShardLock<'a> { // DROP impl isn't required as ShardLock's field types need-drop (std::mem) /// The key/value engine that acts as the in-memory backing store for the database +#[derive(Debug)] pub struct KVEngine { /// the atomic table table: HTable, diff --git a/server/src/util.rs b/server/src/util.rs index a15095b2..b0e6811b 100644 --- a/server/src/util.rs +++ b/server/src/util.rs @@ -73,3 +73,20 @@ macro_rules! consts { )* }; } + +#[macro_export] +macro_rules! typedef { + ($($(#[$attr:meta])* $ident:ident = $ty:ty;)*) => { + $($(#[$attr])* type $ident = $ty;)* + }; + ($($(#[$attr:meta])* $vis:vis $ident:ident = $ty:ty;)*) => { + $($(#[$attr])* $vis type $ident = $ty;)* + }; +} + +#[macro_export] +macro_rules! cfg_test { + ($($item:item)*) => { + $(#[cfg(test)] $item)* + }; +}