use std::collections::HashMap;
use std::sync::Mutex;
use std::sync::atomic::{AtomicBool, Ordering};
use async_trait::async_trait;
use crate::error::{Error, ErrorKind, Result};
use crate::remote::{ChangeBatch, PutOutcome, RemoteEntry, RemoteStore};
#[derive(Clone, Copy, Debug, Default)]
pub struct Faults {
pub offline: f64,
pub rate_limited: f64,
pub lost_response: f64,
pub list_lag: u32,
}
pub const CRASH: &str = "simulated crash";
pub fn is_crash(e: &Error) -> bool {
e.kind() == ErrorKind::Internal && e.message() == CRASH
}
struct State {
rng: u64,
hidden: HashMap<String, u32>,
crash_in: Option<u32>,
}
pub struct FaultyRemote<R> {
inner: R,
faults: Faults,
enabled: AtomicBool,
state: Mutex<State>,
}
impl<R: std::fmt::Debug> std::fmt::Debug for FaultyRemote<R> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FaultyRemote")
.field("inner", &self.inner)
.field("faults", &self.faults)
.finish_non_exhaustive()
}
}
enum Gate {
Pass,
Fail(Error),
CrashAfter,
}
impl<R: RemoteStore> FaultyRemote<R> {
pub fn new(inner: R, faults: Faults, seed: u64) -> Self {
Self {
inner,
faults,
enabled: AtomicBool::new(true),
state: Mutex::new(State {
rng: seed.wrapping_mul(0x9e37_79b9_7f4a_7c15) | 1,
hidden: HashMap::new(),
crash_in: None,
}),
}
}
pub fn set_enabled(&self, on: bool) {
self.enabled.store(on, Ordering::SeqCst);
if !on {
let mut s = self.state.lock().expect("not poisoned");
s.hidden.clear();
s.crash_in = None;
}
}
pub fn crash_after(&self, calls: u32) {
self.state.lock().expect("not poisoned").crash_in = Some(calls);
}
pub fn disarm(&self) {
self.state.lock().expect("not poisoned").crash_in = None;
}
pub fn inner(&self) -> &R {
&self.inner
}
fn chance(s: &mut State, p: f64) -> bool {
s.rng ^= s.rng >> 12;
s.rng ^= s.rng << 25;
s.rng ^= s.rng >> 27;
let x = s.rng.wrapping_mul(0x2545_f491_4f6c_dd1d);
p > 0.0 && ((x >> 11) as f64 / (1u64 << 53) as f64) < p
}
fn gate(&self) -> Gate {
if !self.enabled.load(Ordering::SeqCst) {
return Gate::Pass;
}
let mut s = self.state.lock().expect("not poisoned");
if let Some(n) = s.crash_in {
if n == 0 {
s.crash_in = None;
return Gate::CrashAfter;
}
s.crash_in = Some(n - 1);
}
if Self::chance(&mut s, self.faults.offline) {
return Gate::Fail(Error::new(ErrorKind::Offline, "injected: offline"));
}
if Self::chance(&mut s, self.faults.rate_limited) {
return Gate::Fail(Error::new(ErrorKind::RateLimited, "injected: rate limited"));
}
Gate::Pass
}
fn crash() -> Error {
Error::new(ErrorKind::Internal, CRASH)
}
}
#[async_trait]
impl<R: RemoteStore> RemoteStore for FaultyRemote<R> {
async fn list(&self, prefix: &str) -> Result<Vec<RemoteEntry>> {
let gate = self.gate();
if let Gate::Fail(e) = gate {
return Err(e);
}
let mut entries = self.inner.list(prefix).await?;
{
let mut s = self.state.lock().expect("not poisoned");
entries.retain(|e| !s.hidden.contains_key(&e.key));
s.hidden.retain(|_, n| {
*n = n.saturating_sub(1);
*n > 0
});
}
match gate {
Gate::CrashAfter => Err(Self::crash()),
_ => Ok(entries),
}
}
async fn get(&self, key: &str) -> Result<Option<Vec<u8>>> {
let gate = self.gate();
if let Gate::Fail(e) = gate {
return Err(e);
}
let out = self.inner.get(key).await?;
match gate {
Gate::CrashAfter => Err(Self::crash()),
_ => Ok(out),
}
}
async fn reserve(&self, key: &str) -> Result<Option<String>> {
self.inner.reserve(key).await
}
async fn put_new(
&self,
key: &str,
data: &[u8],
reservation: Option<&str>,
) -> Result<PutOutcome> {
let gate = self.gate();
if let Gate::Fail(e) = gate {
return Err(e);
}
let out = self.inner.put_new(key, data, reservation).await?;
let lost = {
let mut s = self.state.lock().expect("not poisoned");
if out == PutOutcome::Created && self.enabled.load(Ordering::SeqCst) {
let lag = self.faults.list_lag;
if lag > 0 {
let n = 1 + (s.rng % lag as u64) as u32;
s.hidden.insert(key.to_owned(), n);
}
}
self.enabled.load(Ordering::SeqCst) && Self::chance(&mut s, self.faults.lost_response)
};
match gate {
Gate::CrashAfter => Err(Self::crash()),
_ if lost => Err(Error::new(ErrorKind::Offline, "injected: response lost")),
_ => Ok(out),
}
}
async fn delete(&self, key: &str) -> Result<()> {
if let Gate::Fail(e) = self.gate() {
return Err(e);
}
self.inner.delete(key).await
}
async fn changes_since(&self, cursor: Option<&str>) -> Result<Option<ChangeBatch>> {
if let Gate::Fail(e) = self.gate() {
return Err(e);
}
self.inner.changes_since(cursor).await
}
}