use std::{
collections::{HashSet, VecDeque},
future,
sync::{Arc, Mutex},
task::{Context, Poll},
};
use futures::{future::BoxFuture, FutureExt};
use tower::{Service, ServiceExt};
use crate::BoxError;
use super::{CacheKey, Item};
#[derive(Debug)]
struct VerifiedProofs {
keys: HashSet<CacheKey>,
insertion_order: VecDeque<CacheKey>,
capacity: usize,
}
impl VerifiedProofs {
fn new(capacity: usize) -> Self {
Self {
keys: HashSet::with_capacity(capacity),
insertion_order: VecDeque::with_capacity(capacity),
capacity,
}
}
fn contains(&self, key: &CacheKey) -> bool {
self.keys.contains(key)
}
fn insert(&mut self, key: CacheKey) {
if !self.keys.insert(key) {
return;
}
self.insertion_order.push_back(key);
metrics::counter!("zakura.consensus.halo2.cache.insert").increment(1);
while self.insertion_order.len() > self.capacity {
let evicted = self
.insertion_order
.pop_front()
.expect("queue is longer than the capacity, which is at least one");
self.keys.remove(&evicted);
metrics::counter!("zakura.consensus.halo2.cache.evict").increment(1);
}
metrics::gauge!("zakura.consensus.halo2.cache.size").set(self.keys.len() as f64);
}
}
pub struct Cached<S> {
inner: S,
verified: Arc<Mutex<VerifiedProofs>>,
#[cfg(test)]
inner_calls: Arc<Mutex<Vec<CacheKey>>>,
}
impl<S: Clone> Clone for Cached<S> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
verified: self.verified.clone(),
#[cfg(test)]
inner_calls: self.inner_calls.clone(),
}
}
}
impl<S> Cached<S> {
pub(super) fn new(inner: S, capacity: usize) -> Self {
Self {
inner,
verified: Arc::new(Mutex::new(VerifiedProofs::new(capacity))),
#[cfg(test)]
inner_calls: Arc::new(Mutex::new(Vec::new())),
}
}
pub(super) fn inner(&self) -> &S {
&self.inner
}
#[cfg(test)]
pub(super) fn inner_calls_for(&self, item: &Item) -> usize {
let key = item.cache_key();
self.inner_calls
.lock()
.expect("inner call record mutex should not be poisoned")
.iter()
.filter(|called| **called == key)
.count()
}
#[cfg(test)]
pub(super) fn with_inner<T>(&self, inner: T) -> Cached<T> {
Cached {
inner,
verified: self.verified.clone(),
inner_calls: self.inner_calls.clone(),
}
}
}
impl<S> Service<Item> for Cached<S>
where
S: Service<Item, Response = (), Error = BoxError> + Clone + Send + 'static,
S::Future: Send + 'static,
{
type Response = ();
type Error = BoxError;
type Future = BoxFuture<'static, Result<(), BoxError>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, item: Item) -> Self::Future {
let key = item.cache_key();
if self
.verified
.lock()
.expect("verified proof cache mutex should not be poisoned")
.contains(&key)
{
metrics::counter!("zakura.consensus.halo2.cache.hit").increment(1);
return future::ready(Ok(())).boxed();
}
metrics::counter!("zakura.consensus.halo2.cache.miss").increment(1);
let verified = self.verified.clone();
let mut inner = self.inner.clone();
#[cfg(test)]
let inner_calls = self.inner_calls.clone();
async move {
let result = match inner.ready().await {
Ok(inner) => {
#[cfg(test)]
inner_calls
.lock()
.expect("inner call record mutex should not be poisoned")
.push(key);
inner.call(item).await
}
Err(error) => Err(error),
};
if result.is_ok() {
verified
.lock()
.expect("verified proof cache mutex should not be poisoned")
.insert(key);
}
result
}
.boxed()
}
}