mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-19 00:41:03 +00:00
Add storage cache for child trie and notification internals (#2639)
* child cache, and test failing notifications * fix tests and no listen child on top wildcard * remove useless method * bump impl version * Update core/client/src/notifications.rs Co-Authored-By: Tomasz Drwięga <tomusdrw@users.noreply.github.com> * Update core/client/src/notifications.rs Co-Authored-By: Tomasz Drwięga <tomusdrw@users.noreply.github.com> * Update core/client/src/notifications.rs Co-Authored-By: Tomasz Drwięga <tomusdrw@users.noreply.github.com> * Update core/client/src/notifications.rs Co-Authored-By: Tomasz Drwięga <tomusdrw@users.noreply.github.com> * factoring notification methods to remove some redundant code. * test child sub removal * HStorage implementation and some type alias. * Remove HStorage cache: does not fit * fix removal * Make cache use byte length (shared) instead of number of kv * Make use of hashes cache in rpc * applying ratio on different lru caches * Fix format * break a line * Remove per element overhead of lru cache. * typo
This commit is contained in:
@@ -30,18 +30,39 @@ use runtime_primitives::traits::Block as BlockT;
|
||||
#[derive(Debug)]
|
||||
pub struct StorageChangeSet {
|
||||
changes: Arc<Vec<(StorageKey, Option<StorageData>)>>,
|
||||
child_changes: Arc<Vec<(StorageKey, Vec<(StorageKey, Option<StorageData>)>)>>,
|
||||
filter: Option<HashSet<StorageKey>>,
|
||||
child_filters: Option<HashMap<StorageKey, Option<HashSet<StorageKey>>>>,
|
||||
}
|
||||
|
||||
impl StorageChangeSet {
|
||||
/// Convert the change set into iterator over storage items.
|
||||
pub fn iter<'a>(&'a self) -> impl Iterator<Item=&'a (StorageKey, Option<StorageData>)> + 'a {
|
||||
self.changes
|
||||
pub fn iter<'a>(&'a self)
|
||||
-> impl Iterator<Item=(Option<&'a StorageKey>, &'a StorageKey, Option<&'a StorageData>)> + 'a {
|
||||
let top = self.changes
|
||||
.iter()
|
||||
.filter(move |&(key, _)| match self.filter {
|
||||
Some(ref filter) => filter.contains(key),
|
||||
None => true,
|
||||
})
|
||||
.map(move |(k,v)| (None, k, v.as_ref()));
|
||||
let children = self.child_changes
|
||||
.iter()
|
||||
.filter_map(move |(sk, changes)| {
|
||||
if let Some(cf) = self.child_filters.as_ref() {
|
||||
if let Some(filter) = cf.get(sk) {
|
||||
Some(changes
|
||||
.iter()
|
||||
.filter(move |&(key, _)| match filter {
|
||||
Some(ref filter) => filter.contains(key),
|
||||
None => true,
|
||||
})
|
||||
.map(move |(k,v)| (Some(sk), k, v.as_ref())))
|
||||
} else { None }
|
||||
} else { None }
|
||||
})
|
||||
.flatten();
|
||||
top.chain(children)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,9 +77,14 @@ pub struct StorageNotifications<Block: BlockT> {
|
||||
next_id: SubscriberId,
|
||||
wildcard_listeners: FnvHashSet<SubscriberId>,
|
||||
listeners: HashMap<StorageKey, FnvHashSet<SubscriberId>>,
|
||||
child_listeners: HashMap<StorageKey, (
|
||||
HashMap<StorageKey, FnvHashSet<SubscriberId>>,
|
||||
FnvHashSet<SubscriberId>
|
||||
)>,
|
||||
sinks: FnvHashMap<SubscriberId, (
|
||||
mpsc::UnboundedSender<(Block::Hash, StorageChangeSet)>,
|
||||
Option<HashSet<StorageKey>>,
|
||||
Option<HashMap<StorageKey, Option<HashSet<StorageKey>>>>,
|
||||
)>,
|
||||
}
|
||||
|
||||
@@ -68,6 +94,7 @@ impl<Block: BlockT> Default for StorageNotifications<Block> {
|
||||
next_id: Default::default(),
|
||||
wildcard_listeners: Default::default(),
|
||||
listeners: Default::default(),
|
||||
child_listeners: Default::default(),
|
||||
sinks: Default::default(),
|
||||
}
|
||||
}
|
||||
@@ -78,16 +105,24 @@ impl<Block: BlockT> StorageNotifications<Block> {
|
||||
///
|
||||
/// Note the changes are going to be filtered by listener's filter key.
|
||||
/// In fact no event might be sent if clients are not interested in the changes.
|
||||
pub fn trigger(&mut self, hash: &Block::Hash, changeset: impl Iterator<Item=(Vec<u8>, Option<Vec<u8>>)>) {
|
||||
pub fn trigger(
|
||||
&mut self,
|
||||
hash: &Block::Hash,
|
||||
changeset: impl Iterator<Item=(Vec<u8>, Option<Vec<u8>>)>,
|
||||
child_changeset: impl Iterator<
|
||||
Item=(Vec<u8>, impl Iterator<Item=(Vec<u8>, Option<Vec<u8>>)>)
|
||||
>,
|
||||
) {
|
||||
let has_wildcard = !self.wildcard_listeners.is_empty();
|
||||
|
||||
// early exit if no listeners
|
||||
if !has_wildcard && self.listeners.is_empty() {
|
||||
if !has_wildcard && self.listeners.is_empty() && self.child_listeners.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut subscribers = self.wildcard_listeners.clone();
|
||||
let mut changes = Vec::new();
|
||||
let mut child_changes = Vec::new();
|
||||
|
||||
// Collect subscribers and changes
|
||||
for (k, v) in changeset {
|
||||
@@ -102,21 +137,47 @@ impl<Block: BlockT> StorageNotifications<Block> {
|
||||
changes.push((k, v.map(StorageData)));
|
||||
}
|
||||
}
|
||||
for (sk, changeset) in child_changeset {
|
||||
let sk = StorageKey(sk);
|
||||
if let Some((cl, cw)) = self.child_listeners.get(&sk) {
|
||||
let mut changes = Vec::new();
|
||||
for (k, v) in changeset {
|
||||
let k = StorageKey(k);
|
||||
let listeners = cl.get(&k);
|
||||
|
||||
if let Some(ref listeners) = listeners {
|
||||
subscribers.extend(listeners.iter());
|
||||
}
|
||||
|
||||
subscribers.extend(cw.iter());
|
||||
|
||||
if !cw.is_empty() || listeners.is_some() {
|
||||
changes.push((k, v.map(StorageData)));
|
||||
}
|
||||
}
|
||||
if !changes.is_empty() {
|
||||
child_changes.push((sk, changes));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Don't send empty notifications
|
||||
if changes.is_empty() {
|
||||
if changes.is_empty() && child_changes.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let changes = Arc::new(changes);
|
||||
let child_changes = Arc::new(child_changes);
|
||||
// Trigger the events
|
||||
for subscriber in subscribers {
|
||||
let should_remove = {
|
||||
let &(ref sink, ref filter) = self.sinks.get(&subscriber)
|
||||
let &(ref sink, ref filter, ref child_filters) = self.sinks.get(&subscriber)
|
||||
.expect("subscribers returned from self.listeners are always in self.sinks; qed");
|
||||
sink.unbounded_send((hash.clone(), StorageChangeSet {
|
||||
changes: changes.clone(),
|
||||
child_changes: child_changes.clone(),
|
||||
filter: filter.clone(),
|
||||
child_filters: child_filters.clone(),
|
||||
})).is_err()
|
||||
};
|
||||
|
||||
@@ -126,53 +187,120 @@ impl<Block: BlockT> StorageNotifications<Block> {
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_subscriber(&mut self, subscriber: SubscriberId) {
|
||||
if let Some((_, filters)) = self.sinks.remove(&subscriber) {
|
||||
match filters {
|
||||
None => {
|
||||
self.wildcard_listeners.remove(&subscriber);
|
||||
},
|
||||
Some(filters) => {
|
||||
for key in filters {
|
||||
let remove_key = match self.listeners.get_mut(&key) {
|
||||
Some(ref mut set) => {
|
||||
set.remove(&subscriber);
|
||||
set.is_empty()
|
||||
},
|
||||
None => false,
|
||||
};
|
||||
fn remove_subscriber_from(
|
||||
subscriber: &SubscriberId,
|
||||
filters: &Option<HashSet<StorageKey>>,
|
||||
listeners: &mut HashMap<StorageKey, FnvHashSet<SubscriberId>>,
|
||||
wildcards: &mut FnvHashSet<SubscriberId>,
|
||||
){
|
||||
match filters {
|
||||
None => {
|
||||
wildcards.remove(subscriber);
|
||||
},
|
||||
Some(filters) => {
|
||||
|
||||
if remove_key {
|
||||
self.listeners.remove(&key);
|
||||
}
|
||||
for key in filters.iter() {
|
||||
let remove_key = match listeners.get_mut(key) {
|
||||
Some(ref mut set) => {
|
||||
set.remove(subscriber);
|
||||
set.is_empty()
|
||||
},
|
||||
None => false,
|
||||
};
|
||||
|
||||
if remove_key {
|
||||
listeners.remove(key);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Start listening for particular storage keys.
|
||||
pub fn listen(&mut self, filter_keys: Option<&[StorageKey]>) -> StorageEventStream<Block::Hash> {
|
||||
self.next_id += 1;
|
||||
fn remove_subscriber(&mut self, subscriber: SubscriberId) {
|
||||
if let Some((_, filters, child_filters)) = self.sinks.remove(&subscriber) {
|
||||
Self::remove_subscriber_from(
|
||||
&subscriber,
|
||||
&filters,
|
||||
&mut self.listeners,
|
||||
&mut self.wildcard_listeners,
|
||||
);
|
||||
if let Some(child_filters) = child_filters.as_ref() {
|
||||
for (c_key, filters) in child_filters {
|
||||
|
||||
// add subscriber for every key
|
||||
let keys = match filter_keys {
|
||||
if let Some((listeners, wildcards)) = self.child_listeners.get_mut(&c_key) {
|
||||
Self::remove_subscriber_from(
|
||||
&subscriber,
|
||||
&filters,
|
||||
&mut *listeners,
|
||||
&mut *wildcards,
|
||||
);
|
||||
|
||||
if listeners.is_empty() && wildcards.is_empty() {
|
||||
self.child_listeners.remove(&c_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn listen_from(
|
||||
current_id: SubscriberId,
|
||||
filter_keys: &Option<impl AsRef<[StorageKey]>>,
|
||||
listeners: &mut HashMap<StorageKey, FnvHashSet<SubscriberId>>,
|
||||
wildcards: &mut FnvHashSet<SubscriberId>,
|
||||
) -> Option<HashSet<StorageKey>>
|
||||
{
|
||||
match filter_keys {
|
||||
None => {
|
||||
self.wildcard_listeners.insert(self.next_id);
|
||||
wildcards.insert(current_id);
|
||||
None
|
||||
},
|
||||
Some(keys) => Some(keys.iter().map(|key| {
|
||||
self.listeners
|
||||
Some(keys) => Some(keys.as_ref().iter().map(|key| {
|
||||
listeners
|
||||
.entry(key.clone())
|
||||
.or_insert_with(Default::default)
|
||||
.insert(self.next_id);
|
||||
.insert(current_id);
|
||||
key.clone()
|
||||
}).collect())
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Start listening for particular storage keys.
|
||||
pub fn listen(
|
||||
&mut self,
|
||||
filter_keys: Option<&[StorageKey]>,
|
||||
filter_child_keys: Option<&[(StorageKey, Option<Vec<StorageKey>>)]>,
|
||||
) -> StorageEventStream<Block::Hash> {
|
||||
self.next_id += 1;
|
||||
let current_id = self.next_id;
|
||||
|
||||
// add subscriber for every key
|
||||
let keys = Self::listen_from(
|
||||
current_id,
|
||||
&filter_keys,
|
||||
&mut self.listeners,
|
||||
&mut self.wildcard_listeners,
|
||||
);
|
||||
let child_keys = filter_child_keys.map(|filter_child_keys| {
|
||||
filter_child_keys.iter().map(|(c_key, o_keys)| {
|
||||
let (c_listeners, c_wildcards) = self.child_listeners
|
||||
.entry(c_key.clone())
|
||||
.or_insert_with(Default::default);
|
||||
|
||||
(c_key.clone(), Self::listen_from(
|
||||
current_id,
|
||||
o_keys,
|
||||
&mut *c_listeners,
|
||||
&mut *c_wildcards,
|
||||
))
|
||||
}).collect()
|
||||
});
|
||||
|
||||
|
||||
// insert sink
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
self.sinks.insert(self.next_id, (tx, keys));
|
||||
self.sinks.insert(current_id, (tx, keys, child_keys));
|
||||
rx
|
||||
}
|
||||
}
|
||||
@@ -182,13 +310,26 @@ mod tests {
|
||||
use runtime_primitives::testing::{H256 as Hash, Block as RawBlock, ExtrinsicWrapper};
|
||||
use super::*;
|
||||
use futures::Stream;
|
||||
use std::iter::{empty, Empty};
|
||||
|
||||
type TestChangeSet = (
|
||||
Vec<(StorageKey, Option<StorageData>)>,
|
||||
Vec<(StorageKey, Vec<(StorageKey, Option<StorageData>)>)>,
|
||||
);
|
||||
|
||||
#[cfg(test)]
|
||||
impl From<Vec<(StorageKey, Option<StorageData>)>> for StorageChangeSet {
|
||||
fn from(changes: Vec<(StorageKey, Option<StorageData>)>) -> Self {
|
||||
impl From<TestChangeSet> for StorageChangeSet {
|
||||
fn from(changes: TestChangeSet) -> Self {
|
||||
// warning hardcoded child trie wildcard to test upon
|
||||
let child_filters = Some([
|
||||
(StorageKey(vec![4]), None),
|
||||
(StorageKey(vec![5]), None),
|
||||
].into_iter().cloned().collect());
|
||||
StorageChangeSet {
|
||||
changes: Arc::new(changes),
|
||||
changes: Arc::new(changes.0),
|
||||
child_changes: Arc::new(changes.1),
|
||||
filter: None,
|
||||
child_filters,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -206,43 +347,73 @@ mod tests {
|
||||
fn triggering_change_should_notify_wildcard_listeners() {
|
||||
// given
|
||||
let mut notifications = StorageNotifications::<Block>::default();
|
||||
let mut recv = notifications.listen(None).wait();
|
||||
let child_filter = [(StorageKey(vec![4]), None)];
|
||||
let mut recv = notifications.listen(None, Some(&child_filter[..])).wait();
|
||||
|
||||
// when
|
||||
let changeset = vec![
|
||||
(vec![2], Some(vec![3])),
|
||||
(vec![3], None),
|
||||
];
|
||||
notifications.trigger(&Hash::from_low_u64_be(1), changeset.into_iter());
|
||||
let c_changeset_1 = vec![
|
||||
(vec![5], Some(vec![4])),
|
||||
(vec![6], None),
|
||||
];
|
||||
let c_changeset = vec![(vec![4], c_changeset_1)];
|
||||
notifications.trigger(
|
||||
&Hash::from_low_u64_be(1),
|
||||
changeset.into_iter(),
|
||||
c_changeset.into_iter().map(|(a,b)| (a, b.into_iter())),
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(recv.next().unwrap(), Ok((Hash::from_low_u64_be(1), vec![
|
||||
assert_eq!(recv.next().unwrap(), Ok((Hash::from_low_u64_be(1), (vec![
|
||||
(StorageKey(vec![2]), Some(StorageData(vec![3]))),
|
||||
(StorageKey(vec![3]), None),
|
||||
].into())));
|
||||
], vec![(StorageKey(vec![4]), vec![
|
||||
(StorageKey(vec![5]), Some(StorageData(vec![4]))),
|
||||
(StorageKey(vec![6]), None),
|
||||
])]).into())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_only_notify_interested_listeners() {
|
||||
// given
|
||||
let mut notifications = StorageNotifications::<Block>::default();
|
||||
let mut recv1 = notifications.listen(Some(&[StorageKey(vec![1])])).wait();
|
||||
let mut recv2 = notifications.listen(Some(&[StorageKey(vec![2])])).wait();
|
||||
let child_filter = [(StorageKey(vec![4]), Some(vec![StorageKey(vec![5])]))];
|
||||
let mut recv1 = notifications.listen(Some(&[StorageKey(vec![1])]), None).wait();
|
||||
let mut recv2 = notifications.listen(Some(&[StorageKey(vec![2])]), None).wait();
|
||||
let mut recv3 = notifications.listen(Some(&[]), Some(&child_filter)).wait();
|
||||
|
||||
// when
|
||||
let changeset = vec![
|
||||
(vec![2], Some(vec![3])),
|
||||
(vec![1], None),
|
||||
];
|
||||
notifications.trigger(&Hash::from_low_u64_be(1), changeset.into_iter());
|
||||
let c_changeset_1 = vec![
|
||||
(vec![5], Some(vec![4])),
|
||||
(vec![6], None),
|
||||
];
|
||||
|
||||
let c_changeset = vec![(vec![4], c_changeset_1)];
|
||||
notifications.trigger(
|
||||
&Hash::from_low_u64_be(1),
|
||||
changeset.into_iter(),
|
||||
c_changeset.into_iter().map(|(a,b)| (a, b.into_iter())),
|
||||
);
|
||||
|
||||
// then
|
||||
assert_eq!(recv1.next().unwrap(), Ok((Hash::from_low_u64_be(1), vec![
|
||||
assert_eq!(recv1.next().unwrap(), Ok((Hash::from_low_u64_be(1), (vec![
|
||||
(StorageKey(vec![1]), None),
|
||||
].into())));
|
||||
assert_eq!(recv2.next().unwrap(), Ok((Hash::from_low_u64_be(1), vec![
|
||||
], vec![]).into())));
|
||||
assert_eq!(recv2.next().unwrap(), Ok((Hash::from_low_u64_be(1), (vec![
|
||||
(StorageKey(vec![2]), Some(StorageData(vec![3]))),
|
||||
].into())));
|
||||
], vec![]).into())));
|
||||
assert_eq!(recv3.next().unwrap(), Ok((Hash::from_low_u64_be(1), (vec![],
|
||||
vec![
|
||||
(StorageKey(vec![4]), vec![(StorageKey(vec![5]), Some(StorageData(vec![4])))]),
|
||||
]).into())));
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -250,11 +421,14 @@ mod tests {
|
||||
// given
|
||||
let mut notifications = StorageNotifications::<Block>::default();
|
||||
{
|
||||
let _recv1 = notifications.listen(Some(&[StorageKey(vec![1])])).wait();
|
||||
let _recv2 = notifications.listen(Some(&[StorageKey(vec![2])])).wait();
|
||||
let _recv3 = notifications.listen(None).wait();
|
||||
let child_filter = [(StorageKey(vec![4]), Some(vec![StorageKey(vec![5])]))];
|
||||
let _recv1 = notifications.listen(Some(&[StorageKey(vec![1])]), None).wait();
|
||||
let _recv2 = notifications.listen(Some(&[StorageKey(vec![2])]), None).wait();
|
||||
let _recv3 = notifications.listen(None, None).wait();
|
||||
let _recv4 = notifications.listen(None, Some(&child_filter)).wait();
|
||||
assert_eq!(notifications.listeners.len(), 2);
|
||||
assert_eq!(notifications.wildcard_listeners.len(), 1);
|
||||
assert_eq!(notifications.wildcard_listeners.len(), 2);
|
||||
assert_eq!(notifications.child_listeners.len(), 1);
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -262,11 +436,13 @@ mod tests {
|
||||
(vec![2], Some(vec![3])),
|
||||
(vec![1], None),
|
||||
];
|
||||
notifications.trigger(&Hash::from_low_u64_be(1), changeset.into_iter());
|
||||
let c_changeset = empty::<(_, Empty<_>)>();
|
||||
notifications.trigger(&Hash::from_low_u64_be(1), changeset.into_iter(), c_changeset);
|
||||
|
||||
// then
|
||||
assert_eq!(notifications.listeners.len(), 0);
|
||||
assert_eq!(notifications.wildcard_listeners.len(), 0);
|
||||
assert_eq!(notifications.child_listeners.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -274,11 +450,12 @@ mod tests {
|
||||
// given
|
||||
let mut recv = {
|
||||
let mut notifications = StorageNotifications::<Block>::default();
|
||||
let recv = notifications.listen(None).wait();
|
||||
let recv = notifications.listen(None, None).wait();
|
||||
|
||||
// when
|
||||
let changeset = vec![];
|
||||
notifications.trigger(&Hash::from_low_u64_be(1), changeset.into_iter());
|
||||
let c_changeset = empty::<(_, Empty<_>)>();
|
||||
notifications.trigger(&Hash::from_low_u64_be(1), changeset.into_iter(), c_changeset);
|
||||
recv
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user