use std::time::{Duration, Instant};
use std::collections::HashMap;
use std::fmt;
use super::{ResponseError, ValidityError};
#[derive(Debug, Eq, PartialEq)]
pub enum Error {
NoMajority(usize),
Majority(Inner, usize, usize),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::Majority(err, majority, total) => {
write!(f, "Error cause was {:?}, (majority count: {} / total: {})",
err, majority, total)
}
Error::NoMajority(total) => {
write!(f, "Error cause couldn't be determined, the total number of responses was {}", total)
}
}
}
}
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
pub enum Inner {
BadProof,
Decoder,
EmptyResponse,
HeaderByNumber,
TooFewResults,
TooManyResults,
Trie,
UnresolvedHeader,
Unexpected,
WrongHash,
WrongHeaderSequence,
WrongKind,
WrongNumber,
WrongTrieRoot,
}
#[derive(Debug)]
pub struct ResponseGuard {
request_start: Instant,
time_to_live: Duration,
responses: HashMap<Inner, usize>,
number_responses: usize,
}
impl ResponseGuard {
pub fn new(time_to_live: Duration) -> Self {
Self {
request_start: Instant::now(),
time_to_live,
responses: HashMap::new(),
number_responses: 0,
}
}
fn into_reason(&self, err: &ResponseError<super::request::Error>) -> Inner {
match err {
ResponseError::Unexpected => Inner::Unexpected,
ResponseError::Validity(ValidityError::BadProof) => Inner::BadProof,
ResponseError::Validity(ValidityError::Decoder(_)) => Inner::Decoder,
ResponseError::Validity(ValidityError::Empty) => Inner::EmptyResponse,
ResponseError::Validity(ValidityError::HeaderByNumber) => Inner::HeaderByNumber,
ResponseError::Validity(ValidityError::TooFewResults(_, _)) => Inner::TooFewResults,
ResponseError::Validity(ValidityError::TooManyResults(_, _)) => Inner::TooManyResults,
ResponseError::Validity(ValidityError::Trie(_)) => Inner::Trie,
ResponseError::Validity(ValidityError::UnresolvedHeader(_)) => Inner::UnresolvedHeader,
ResponseError::Validity(ValidityError::WrongHash(_, _)) => Inner::WrongHash,
ResponseError::Validity(ValidityError::WrongHeaderSequence) => Inner::WrongHeaderSequence,
ResponseError::Validity(ValidityError::WrongKind) => Inner::WrongKind,
ResponseError::Validity(ValidityError::WrongNumber(_, _)) => Inner::WrongNumber,
ResponseError::Validity(ValidityError::WrongTrieRoot(_, _)) => Inner::WrongTrieRoot,
}
}
pub fn register_error(&mut self, err: &ResponseError<super::request::Error>) -> Result<(), Error> {
let err = self.into_reason(err);
*self.responses.entry(err).or_insert(0) += 1;
self.number_responses = self.number_responses.saturating_add(1);
trace!(target: "circuit_breaker", "ResponseGuard: {:?}", self.responses);
if self.request_start.elapsed() >= self.time_to_live {
let (&err, &max_count) = self.responses.iter().max_by_key(|(_k, v)| *v).expect("got at least one element; qed");
let majority = self.responses.values().filter(|v| **v == max_count).count() == 1;
if majority {
Err(Error::Majority(err, max_count, self.number_responses))
} else {
Err(Error::NoMajority(self.number_responses))
}
} else {
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use std::thread;
use super::*;
#[test]
fn test_basic_by_majority() {
let mut guard = ResponseGuard::new(Duration::from_secs(5));
guard.register_error(&ResponseError::Validity(ValidityError::Empty)).unwrap();
guard.register_error(&ResponseError::Unexpected).unwrap();
guard.register_error(&ResponseError::Unexpected).unwrap();
guard.register_error(&ResponseError::Unexpected).unwrap();
thread::sleep(Duration::from_secs(5));
assert_eq!(guard.register_error(&ResponseError::Validity(ValidityError::WrongKind)), Err(Error::Majority(Inner::Unexpected, 3, 5)));
}
#[test]
fn test_no_majority() {
let mut guard = ResponseGuard::new(Duration::from_secs(5));
guard.register_error(&ResponseError::Validity(ValidityError::Empty)).unwrap();
guard.register_error(&ResponseError::Validity(ValidityError::Empty)).unwrap();
guard.register_error(&ResponseError::Unexpected).unwrap();
guard.register_error(&ResponseError::Unexpected).unwrap();
thread::sleep(Duration::from_secs(5));
assert_eq!(guard.register_error(&ResponseError::Validity(ValidityError::WrongKind)), Err(Error::NoMajority(5)));
}
}