Commit be555eb9 authored by Hero Bird's avatar Hero Bird
Browse files

[pdsl_core] Implement flushing for SyncCell and SyncChunk

parent db20a6cf
......@@ -369,7 +369,6 @@ where
{
/// Removes the value from the cell.
pub fn clear(&mut self) {
self.cell.clear(); // TODO: Removes this after implementation of flushing
self.cache.update(None);
self.cache.mark_dirty();
}
......@@ -395,7 +394,6 @@ where
{
/// Sets the value of the cell.
pub fn set(&mut self, val: T) {
self.cell.store(&val); // TODO: Removes this after implementation of flushing
self.cache.update(Some(val));
self.cache.mark_dirty();
}
......@@ -434,16 +432,19 @@ where
#[cfg(all(test, feature = "test-env"))]
mod tests {
use super::*;
use crate::storage::Key;
use crate::{
storage::{
Key,
alloc::ForwardAlloc,
},
test_utils::run_test,
env::TestEnv,
};
fn dummy_cell() -> SyncCell<i32> {
unsafe {
let mut alloc = crate::storage::alloc::ForwardAlloc::from_raw_parts(
let mut alloc = ForwardAlloc::from_raw_parts(
Key([0x0; 32])
);
SyncCell::new_using_alloc(&mut alloc)
......@@ -465,28 +466,98 @@ mod tests {
}
#[test]
fn count_reads() {
run_test(|| {
let cell = dummy_cell();
assert_eq!(TestEnv::total_reads(), 0);
fn count_rw_get() {
// Repetitions performed.
const N: u32 = 5;
let mut cell = dummy_cell();
// Asserts initial reads and writes are zero.
assert_eq!(TestEnv::total_reads(), 0);
assert_eq!(TestEnv::total_writes(), 0);
// Repeated reads on the same cell.
for _i in 0..N {
cell.get();
assert_eq!(TestEnv::total_reads(), 1);
cell.get();
cell.get();
assert_eq!(TestEnv::total_writes(), 0);
}
// Flush the cell and assert reads and writes.
cell.flush();
assert_eq!(TestEnv::total_reads(), 1);
assert_eq!(TestEnv::total_writes(), 0);
}
#[test]
fn count_rw_get_mut() {
// Repetitions performed.
const N: u32 = 5;
let mut cell = dummy_cell();
// Asserts initial reads and writes are zero.
assert_eq!(TestEnv::total_reads(), 0);
assert_eq!(TestEnv::total_writes(), 0);
// Repeated mutable reads on the same cell.
for _i in 0..N {
cell.get_mut();
assert_eq!(TestEnv::total_reads(), 1);
})
assert_eq!(TestEnv::total_writes(), 0);
}
// Flush the cell and assert reads and writes.
cell.flush();
assert_eq!(TestEnv::total_reads(), 1);
assert_eq!(TestEnv::total_writes(), 1);
}
#[test]
fn count_writes() {
run_test(|| {
let mut cell = dummy_cell();
fn count_rw_set() {
// Repetitions performed.
const N: u32 = 5;
let mut cell = dummy_cell();
// Asserts initial reads and writes are zero.
assert_eq!(TestEnv::total_reads(), 0);
assert_eq!(TestEnv::total_writes(), 0);
// Repeated writes to the same cell.
for _i in 0..N {
cell.set(42);
assert_eq!(TestEnv::total_reads(), 0);
assert_eq!(TestEnv::total_writes(), 0);
cell.set(1);
assert_eq!(TestEnv::total_writes(), 1);
cell.set(2);
cell.set(3);
assert_eq!(TestEnv::total_writes(), 3);
})
}
// Flush the cell and assert reads and writes.
cell.flush();
assert_eq!(TestEnv::total_reads(), 0);
assert_eq!(TestEnv::total_writes(), 1);
}
#[test]
fn count_rw_clear() {
// Repetitions performed.
const N: u32 = 5;
let mut cell = dummy_cell();
// Asserts initial reads and writes are zero.
assert_eq!(TestEnv::total_reads(), 0);
assert_eq!(TestEnv::total_writes(), 0);
// Repeated writes to the same cell.
for _i in 0..N {
cell.clear();
assert_eq!(TestEnv::total_reads(), 0);
assert_eq!(TestEnv::total_writes(), 0);
}
// Flush the cell and assert reads and writes.
cell.flush();
assert_eq!(TestEnv::total_reads(), 0);
assert_eq!(TestEnv::total_writes(), 1);
}
}
......@@ -22,7 +22,6 @@ mod sync_chunk;
pub(crate) use self::{
raw_chunk::RawChunkCell,
typed_chunk::TypedChunkCell,
};
pub use self::{
......
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
// This file is part of pDSL.
//
// pDSL is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// pDSL 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with pDSL. If not, see <http://www.gnu.org/licenses/>.
use crate::{
storage::{
Key,
chunk::{
TypedChunk,
TypedChunkCell,
},
Allocator,
},
memory::collections::btree_map::{
BTreeMap,
Entry,
},
};
use core::cell::RefCell;
/// A chunk of synchronized cells.
///
/// Provides mutable and read-optimized access to the associated constract storage slot.
///
/// # Guarantees
///
/// - `Owned`
/// - `Typed`
/// - `Opt. Reads`
/// - `Mutable`
///
/// Read more about kinds of guarantees and their effect [here](../index.html#guarantees).
#[derive(Debug)]
pub struct SyncChunk<T> {
/// The underlying chunk of cells.
chunk: TypedChunk<T>,
/// The cached element.
elems: Cache<T>,
}
/// A single cache entry for a copy chunk cell.
#[cfg(not(feature = "std"))]
type CacheEntry<'a, T> = Entry<'a, u32, Option<T>>;
/// A single cache entry for a copy chunk cell.
#[cfg(feature = "std")]
type CacheEntry<'a, T> = Entry<'a, u32, Option<T>>;
/// A single cell within a chunk of copy cells.
#[derive(Debug)]
pub struct SyncChunkCell<'a, T> {
/// The underlying cell within the chunk of cells.
cell: TypedChunkCell<'a, T>,
/// The cached entry for the cell.
elem: CacheEntry<'a, T>,
}
impl<'a, T> SyncChunkCell<'a, T> {
/// Creates a new cell within a chunk of copy cells.
///
/// # Safety
///
/// This is unsafe since it doesn't check aliasing of cells
/// or if the cell and the cache entry are actually associated
/// with each other.
pub(self) unsafe fn new_unchecked(
cell: TypedChunkCell<'a, T>,
elem: CacheEntry<'a, T>
) -> Self {
Self{cell, elem}
}
/// Removes the value stored in this cell.
pub fn clear(self) {
let mut this = self;
match this.elem {
Entry::Occupied(mut occupied) => {
this.cell.clear();
occupied.insert(None);
}
Entry::Vacant(vacant) => {
this.cell.clear();
vacant.insert(None);
}
}
}
}
impl<'a, T> SyncChunkCell<'a, T>
where
T: parity_codec::Decode
{
/// Removes the value from the cell and returns the removed value.
///
/// # Note
///
/// Prefer using `clear` if you are not interested in the return value.
#[must_use]
pub fn remove(self) -> Option<T> {
let mut this = self;
match this.elem {
Entry::Occupied(mut occupied) => {
this.cell.clear();
occupied.insert(None)
}
Entry::Vacant(vacant) => {
let old = this.cell.load();
this.cell.clear();
vacant.insert(None);
old
}
}
}
}
impl<'a, T> SyncChunkCell<'a, T>
where
T: parity_codec::Encode
{
/// Stores the new value into the cell.
pub fn set(self, val: T) {
let mut this = self;
match this.elem {
Entry::Occupied(mut occupied) => {
this.cell.store(&val);
occupied.insert(Some(val));
}
Entry::Vacant(vacant) => {
this.cell.store(&val);
vacant.insert(Some(val));
}
}
}
}
impl<'a, T> SyncChunkCell<'a, T>
where
T: parity_codec::Codec
{
/// Mutates the value of this cell.
///
/// Returns an immutable reference to the result if
/// a mutation happened, otherwise `None` is returned.
///
/// # Note
///
/// Prefer using `set` if you are not interested in the return value.
pub fn mutate_with<F>(self, f: F) -> Option<&'a T>
where
F: FnOnce(&mut T)
{
let mut this = self;
match this.elem {
Entry::Occupied(occupied) => {
if let Some(elem) = occupied.into_mut() {
f(elem);
this.cell.store(elem);
return Some(&*elem)
}
None
}
Entry::Vacant(vacant) => {
let mut ret = false;
let mut elem = this.cell.load();
if let Some(elem) = &mut elem {
f(elem);
this.cell.store(&*elem);
ret = true;
}
let res = (&*vacant.insert(elem)).into();
if ret {
return res
}
None
}
}
}
/// Replaces the value of this cell and returns its previous value.
///
/// # Note
///
/// Prefer using `set` if you are not interested in the return value.
#[must_use]
pub fn replace(self, val: T) -> Option<T> {
let mut this = self;
match this.elem {
Entry::Occupied(mut occupied) => {
this.cell.store(&val);
occupied.insert(Some(val))
}
Entry::Vacant(vacant) => {
let old = this.cell.load();
this.cell.store(&val);
vacant.insert(Some(val));
old
}
}
}
}
/// Stores the values of synchronized cells.
///
/// # Note
///
/// An element counts as synchronized if its version in the contract
/// storage and the version in the cache are identical.
#[derive(Debug, PartialEq, Eq)]
struct Cache<T> {
/// The synchronized values of associated cells.
elems: RefCell<BTreeMap<u32, Option<T>>>,
}
impl<T> Default for Cache<T> {
fn default() -> Self {
Self{ elems: RefCell::new(BTreeMap::new()) }
}
}
/// A cached entity.
///
/// This is either in sync with the contract storage or out of sync.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Cached<T> {
Desync,
Sync(Option<T>),
}
impl<T> Cache<T> {
/// Inserts or updates a value associated with the `n`-th cell.
///
/// Returns an immutable reference to the new value.
pub fn upsert(&self, n: u32, val: Option<T>) -> Option<&T> {
let elems: &mut BTreeMap<u32, Option<T>> = unsafe {
&mut *self.elems.as_ptr()
};
match elems.entry(n) {
Entry::Occupied(mut occupied) => {
occupied.insert(val);
(&*occupied.into_mut()).into()
}
Entry::Vacant(vacant) => {
(&*vacant.insert(val)).into()
}
}
}
/// Returns the synchronized value of the `n`-th cell if any.
pub fn get(&self, n: u32) -> Cached<&T> {
let elems: &mut BTreeMap<u32, Option<T>> = unsafe {
&mut *self.elems.as_ptr()
};
match elems.get(&n) {
Some(opt_elem) => Cached::Sync(opt_elem.into()),
None => Cached::Desync,
}
}
/// Returns the cache entry for the `n`-th cell.
pub fn entry(&mut self, n: u32) -> CacheEntry<T> {
self.elems.get_mut().entry(n)
}
}
impl<T> parity_codec::Encode for SyncChunk<T> {
fn encode_to<W: parity_codec::Output>(&self, dest: &mut W) {
self.chunk.encode_to(dest)
}
}
impl<T> parity_codec::Decode for SyncChunk<T> {
fn decode<I: parity_codec::Input>(input: &mut I) -> Option<Self> {
TypedChunk::decode(input)
.map(|typed_chunk| Self{
chunk: typed_chunk,
elems: Cache::default(),
})
}
}
impl<T> SyncChunk<T> {
/// Allocates a new sync cell chunk using the given storage allocator.
///
/// # Safety
///
/// The is unsafe because it does not check if the associated storage
/// does not alias with storage allocated by other storage allocators.
pub unsafe fn new_using_alloc<A>(alloc: &mut A) -> Self
where
A: Allocator
{
Self{
chunk: TypedChunk::new_using_alloc(alloc),
elems: Cache::default(),
}
}
/// Returns the unterlying key to the cells.
///
/// # Note
///
/// This is a low-level utility getter and should
/// normally not be required by users.
pub fn cells_key(&self) -> Key {
self.chunk.cells_key()
}
/// Returns an accessor to the `n`-th cell.
fn cell_at(&mut self, n: u32) -> SyncChunkCell<T> {
unsafe {
SyncChunkCell::new_unchecked(
self.chunk.cell_at(n),
self.elems.entry(n)
)
}
}
/// Clear the `n`-th cell.
///
/// # Errors
///
/// If `n` is out of bounds.
pub fn clear(&mut self, n: u32) {
self.cell_at(n).clear()
}
}
impl<T> SyncChunk<T>
where
T: parity_codec::Decode
{
/// Returns the value of the `n`-th cell if any.
///
/// # Errors
///
/// If `n` is out of bounds.
pub fn get(&self, n: u32) -> Option<&T> {
if let Cached::Sync(cached) = self.elems.get(n) {
return cached
}
self.load(n)
}
/// Returns the value of the `n`-th cell if any.
///
/// # Note
///
/// Prefer using [`get`](struct.SyncChunk.html#method.get)
/// to avoid unnecesary contract storage accesses.
///
/// # Errors
///
/// If `n` is out of bounds.
fn load(&self, n: u32) -> Option<&T> {
self.elems.upsert(
n,
self.chunk.load(n)
)
}
/// Clears the `n`-th cell and returns its previous value if any.
///
/// # Note
///
/// Use [`clear`](struct.SyncChunk.html#method.clear) instead
/// if you are not interested in the old return value.
///
/// # Errors
///
/// If `n` is out of bounds.
#[must_use]
pub fn remove(&mut self, n: u32) -> Option<T> {
self.cell_at(n).remove()
}
}
impl<T> SyncChunk<T>
where
T: parity_codec::Encode
{
/// Sets the value of the `n`-th cell.
///
/// # Errors
///
/// If `n` is out of bounds.
pub fn set(&mut self, n: u32, val: T) {
self.cell_at(n).set(val)
}
}
impl<T> SyncChunk<T>
where
T: parity_codec::Codec
{
/// Sets the value of the `n`-th cell and returns its old value if any.
///
/// # Note
///
/// Use [`set`](struct.SyncChunk.html#method.set) instead
/// if you are not interested in the old return value.
///
/// # Errors
///
/// If `n` is out of bounds.
#[must_use]
pub fn replace(&mut self, n: u32, val: T) -> Option<T> {
self.cell_at(n).replace(val)
}
/// Mutates the value of the `n`-th cell if any.
///
/// Returns an immutable reference to the result if
/// a mutation happened, otherwise `None` is returned.
///
/// # Errors
///
/// If `n` is out of bounds.
pub fn mutate_with<F>(&mut self, n: u32, f: F) -> Option<&T>
where
F: FnOnce(&mut T)
{
self.cell_at(n).mutate_with(f)
}
}
#[cfg(all(test, feature = "test-env"))]
mod tests {
use super::*;
use crate::{
test_utils::run_test,
env::TestEnv,
};
fn dummy_chunk() -> SyncChunk<u32> {
unsafe {
let mut alloc = crate::storage::alloc::ForwardAlloc::from_raw_parts(
Key([0x0; 32])
);
SyncChunk::new_using_alloc(&mut alloc)