Files
pezkuwi-subxt/substrate/client/db/src/offchain.rs
T
Bernhard Schuster 95d73994c1 offchain storage lock (#6010)
* feat/offchain/storage: add remove interface method

* feat/offchain/storeage: add remote to StorageValueRef

* feat/offchain/storage: add storage lock

* fix/review: Apply suggestions from code review

Co-authored-by: Tomasz Drwięga <tomusdrw@users.noreply.github.com>
Co-authored-by: Peter Goodspeed-Niklaus <coriolinus@users.noreply.github.com>

* refactor/offchain/storage/lock: introduce `Lockable` trait part 1 of 2

* chore/offchain/rename: _remove -> clean

* feat/offchain/storage/lock: add TimeAndBlock based part 2 of 2

* fix/offchain/storage/lock: block and time expiry must be && not ||

* chore/offchain/storage: minor fmt doc comments

* doc/comment: prefer markdown emphasis over CAPS

* doc/comment: rewrap multiline module level docs

* doc/comment: rephrase

* impl sleep_until and use the actual time for the test env

* feat/test: add more tests, ignore some sample impl doctests

* fix/review: Apply suggestions from code review

Co-authored-by: Nikolay Volf <nikvolf@gmail.com>

* doc/comment: better description

* fix/review: Apply suggestions from code review

Co-authored-by: Nikolay Volf <nikvolf@gmail.com>

* chore/storage: lifetime cleanup

* fix/cleanup: trait bounds, cargo-spellcheck + extra explanations

* fix/doc: periods +-

* fix/review: Apply suggestions from code review

Co-authored-by: Tomasz Drwięga <tomusdrw@users.noreply.github.com>

* cleanup: remove explicit lifetime bound, copy -> clone

* fix/review: make trait Lockable contain only static, try_lock should not return Err(Option<L>),

* chore/lifetimes: remove a couple of lifetime bounds which the compiler can figure out

* refactor: migrate to an instant based

* fix/feedback: fix, reduce, rename, docs update pending

* docs/reword: adjust to changed code

* fix/offchain/testing: timestamp and sleep_until shall not block

* chore/lines: lines must < 100 chars

* fix/docs: add missing pub field doc comments

* refactor/x: try_lock does not need to return an Option<_>

* refactor/simplify: a better way of waiting for a lock to resolve

* docs: consistency

* fix/line: < 100

* fix/doctest/use: avoid crate::

* fix/doctest: *

* fix/review: remove unused trait bound

* fix/review: pretty by const fn

* fix/review: reduce default timeout to 20s

* docs: grammar

* fix/review: add with_block_deadline

* doc: revamp BlockNumberProvider documentation to be less frame centric

* chore: fmt

* docs: add missing doc comment

Co-authored-by: Bernhard Schuster <bernhard@parity.io>
Co-authored-by: Tomasz Drwięga <tomusdrw@users.noreply.github.com>
Co-authored-by: Peter Goodspeed-Niklaus <coriolinus@users.noreply.github.com>
Co-authored-by: Nikolay Volf <nikvolf@gmail.com>
2020-05-28 14:26:11 +02:00

152 lines
4.1 KiB
Rust

// This file is part of Substrate.
// Copyright (C) 2017-2020 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// 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 General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! RocksDB-based offchain workers local storage.
use std::{
collections::HashMap,
sync::Arc,
};
use crate::{columns, Database, DbHash, Transaction};
use parking_lot::Mutex;
/// Offchain local storage
#[derive(Clone)]
pub struct LocalStorage {
db: Arc<dyn Database<DbHash>>,
locks: Arc<Mutex<HashMap<Vec<u8>, Arc<Mutex<()>>>>>,
}
impl std::fmt::Debug for LocalStorage {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
fmt.debug_struct("LocalStorage")
.finish()
}
}
impl LocalStorage {
/// Create new offchain storage for tests (backed by memorydb)
#[cfg(any(test, feature = "test-helpers"))]
pub fn new_test() -> Self {
let db = kvdb_memorydb::create(crate::utils::NUM_COLUMNS);
let db = sp_database::as_database(db);
Self::new(db as _)
}
/// Create offchain local storage with given `KeyValueDB` backend.
pub fn new(db: Arc<dyn Database<DbHash>>) -> Self {
Self {
db,
locks: Default::default(),
}
}
}
impl sp_core::offchain::OffchainStorage for LocalStorage {
fn set(&mut self, prefix: &[u8], key: &[u8], value: &[u8]) {
let key: Vec<u8> = prefix.iter().chain(key).cloned().collect();
let mut tx = Transaction::new();
tx.set(columns::OFFCHAIN, &key, value);
self.db.commit(tx);
}
fn remove(&mut self, prefix: &[u8], key: &[u8]) {
let key: Vec<u8> = prefix.iter().chain(key).cloned().collect();
let mut tx = Transaction::new();
tx.remove(columns::OFFCHAIN, &key);
self.db.commit(tx);
}
fn get(&self, prefix: &[u8], key: &[u8]) -> Option<Vec<u8>> {
let key: Vec<u8> = prefix.iter().chain(key).cloned().collect();
self.db.get(columns::OFFCHAIN, &key)
}
fn compare_and_set(
&mut self,
prefix: &[u8],
item_key: &[u8],
old_value: Option<&[u8]>,
new_value: &[u8],
) -> bool {
let key: Vec<u8> = prefix.iter().chain(item_key).cloned().collect();
let key_lock = {
let mut locks = self.locks.lock();
locks.entry(key.clone()).or_default().clone()
};
let is_set;
{
let _key_guard = key_lock.lock();
let val = self.db.get(columns::OFFCHAIN, &key);
is_set = val.as_ref().map(|x| &**x) == old_value;
if is_set {
self.set(prefix, item_key, new_value)
}
}
// clean the lock map if we're the only entry
let mut locks = self.locks.lock();
{
drop(key_lock);
let key_lock = locks.get_mut(&key);
if let Some(_) = key_lock.and_then(Arc::get_mut) {
locks.remove(&key);
}
}
is_set
}
}
#[cfg(test)]
mod tests {
use super::*;
use sp_core::offchain::OffchainStorage;
#[test]
fn should_compare_and_set_and_clear_the_locks_map() {
let mut storage = LocalStorage::new_test();
let prefix = b"prefix";
let key = b"key";
let value = b"value";
storage.set(prefix, key, value);
assert_eq!(storage.get(prefix, key), Some(value.to_vec()));
assert_eq!(storage.compare_and_set(prefix, key, Some(value), b"asd"), true);
assert_eq!(storage.get(prefix, key), Some(b"asd".to_vec()));
assert!(storage.locks.lock().is_empty(), "Locks map should be empty!");
}
#[test]
fn should_compare_and_set_on_empty_field() {
let mut storage = LocalStorage::new_test();
let prefix = b"prefix";
let key = b"key";
assert_eq!(storage.compare_and_set(prefix, key, None, b"asd"), true);
assert_eq!(storage.get(prefix, key), Some(b"asd".to_vec()));
assert!(storage.locks.lock().is_empty(), "Locks map should be empty!");
}
}