Skip to main content

objstore_r/
mem.rs

1// Author: support@lituus.io
2// Copyright: lituus-io, all rights reserved
3//
4//! An in-memory store with scripted fault injection, for tests.
5//!
6//! Faults are queued with [`Mem::inject`] and consumed one per operation, in
7//! order. [`Fault::LostResponse`] applies the operation and then reports it as
8//! failed: the classic ambiguity a real network adds, which crash-consistent
9//! callers must survive.
10
11use crate::key::check_prefix;
12use crate::{clip, Bytes, Cond, Error, Key, Meta, Object, ObjectStore, Result};
13use std::collections::{BTreeMap, VecDeque};
14use std::future::{ready, Ready};
15use std::ops::Range;
16use std::sync::Mutex;
17
18/// A scripted outcome for one future operation.
19#[derive(Debug)]
20#[non_exhaustive]
21pub enum Fault {
22    /// Let the operation through untouched (a placeholder that lets a real
23    /// fault target the n-th operation; see [`Mem::inject_after`]).
24    Pass,
25    /// Fail without applying the operation.
26    Fail(Error),
27    /// Apply the operation, then report this error.
28    LostResponse(Error),
29}
30
31#[derive(Debug, Default)]
32struct Inner {
33    objects: BTreeMap<String, Object>,
34    next_generation: u64,
35    faults: VecDeque<Fault>,
36    ops: u64,
37}
38
39/// An in-memory [`ObjectStore`]. Generations increase monotonically across
40/// the whole store, like GCS.
41#[derive(Debug, Default)]
42pub struct Mem {
43    inner: Mutex<Inner>,
44}
45
46impl Mem {
47    /// Queue a fault for a future operation (consumed in order, one per call).
48    pub fn inject(&self, fault: Fault) {
49        self.lock().faults.push_back(fault);
50    }
51
52    /// Let `skip` operations through, then apply `fault` — for crash tests
53    /// that fail each step of a multi-operation protocol in turn.
54    pub fn inject_after(&self, skip: u64, fault: Fault) {
55        let mut g = self.lock();
56        for _ in 0..skip {
57            g.faults.push_back(Fault::Pass);
58        }
59        g.faults.push_back(fault);
60    }
61
62    /// Drop every queued fault.
63    pub fn clear_faults(&self) {
64        self.lock().faults.clear();
65    }
66
67    /// Operations served so far (every call, failed or not).
68    #[must_use]
69    pub fn ops(&self) -> u64 {
70        self.lock().ops
71    }
72
73    /// Every key, sorted.
74    #[must_use]
75    pub fn keys(&self) -> Vec<String> {
76        self.lock().objects.keys().cloned().collect()
77    }
78
79    fn lock(&self) -> std::sync::MutexGuard<'_, Inner> {
80        // A panic while holding the lock can only come from a bug in this
81        // module; the data is still consistent (every mutation is one insert
82        // or remove), so poisoning is ignored.
83        self.inner.lock().unwrap_or_else(std::sync::PoisonError::into_inner)
84    }
85
86    fn run<T>(&self, op: impl FnOnce(&mut Inner) -> Result<T>) -> Result<T> {
87        let mut g = self.lock();
88        g.ops += 1;
89        match g.faults.pop_front() {
90            Some(Fault::Fail(e)) => Err(e),
91            Some(Fault::LostResponse(e)) => {
92                let _ = op(&mut g);
93                Err(e)
94            }
95            Some(Fault::Pass) | None => op(&mut g),
96        }
97    }
98}
99
100impl ObjectStore for Mem {
101    type Get<'a> = Ready<Result<Option<Object>>>;
102    type GetRange<'a> = Ready<Result<Bytes>>;
103    type Head<'a> = Ready<Result<Option<Meta>>>;
104    type Put<'a> = Ready<Result<u64>>;
105    type List<'a> = Ready<Result<Vec<Meta>>>;
106    type Delete<'a> = Ready<Result<()>>;
107
108    fn get<'a>(&'a self, key: &'a str) -> Self::Get<'a> {
109        ready(Key::parse(key).and_then(|k| self.run(|g| Ok(g.objects.get(k.as_str()).cloned()))))
110    }
111
112    fn get_range<'a>(&'a self, key: &'a str, range: Range<u64>) -> Self::GetRange<'a> {
113        ready(Key::parse(key).and_then(|k| {
114            self.run(|g| {
115                let o = g.objects.get(k.as_str()).ok_or(Error::NotFound)?;
116                Ok(o.bytes.slice(clip(&range, o.bytes.len() as u64)))
117            })
118        }))
119    }
120
121    fn head<'a>(&'a self, key: &'a str) -> Self::Head<'a> {
122        ready(Key::parse(key).and_then(|k| {
123            self.run(|g| {
124                Ok(g.objects.get(k.as_str()).map(|o| Meta {
125                    key: k.as_str().to_owned(),
126                    size: o.bytes.len() as u64,
127                    generation: o.generation,
128                }))
129            })
130        }))
131    }
132
133    fn put<'a>(&'a self, key: &'a str, body: Bytes, cond: Cond) -> Self::Put<'a> {
134        ready(Key::parse(key).and_then(|k| {
135            self.run(|g| {
136                let current = g.objects.get(k.as_str()).map(|o| o.generation);
137                match (cond, current) {
138                    (Cond::CreateOnly, Some(_)) => return Err(Error::Conflict),
139                    (Cond::IfGeneration(want), cur) if cur != Some(want) => {
140                        return Err(Error::PreconditionFailed)
141                    }
142                    _ => {}
143                }
144                g.next_generation += 1;
145                let generation = g.next_generation;
146                g.objects.insert(k.as_str().to_owned(), Object { bytes: body, generation });
147                Ok(generation)
148            })
149        }))
150    }
151
152    fn list<'a>(&'a self, prefix: &'a str) -> Self::List<'a> {
153        ready(check_prefix(prefix).and_then(|()| {
154            self.run(|g| {
155                Ok(g.objects
156                    .range(prefix.to_owned()..)
157                    .take_while(|(k, _)| k.starts_with(prefix))
158                    .map(|(k, o)| Meta {
159                        key: k.clone(),
160                        size: o.bytes.len() as u64,
161                        generation: o.generation,
162                    })
163                    .collect())
164            })
165        }))
166    }
167
168    fn delete<'a>(&'a self, key: &'a str) -> Self::Delete<'a> {
169        ready(Key::parse(key).and_then(|k| {
170            self.run(|g| {
171                g.objects.remove(k.as_str());
172                Ok(())
173            })
174        }))
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181
182    #[test]
183    fn inject_after_targets_the_nth_operation() {
184        let m = Mem::default();
185        m.inject_after(2, Fault::Fail(Error::unavailable("boom", true)));
186        let put = |k| futures::executor::block_on(m.put(k, Bytes::new(), Cond::None));
187        assert!(put("a").is_ok());
188        assert!(put("b").is_ok());
189        assert!(put("c").is_err());
190        assert!(put("d").is_ok());
191    }
192    use futures::executor::block_on;
193
194    #[test]
195    fn faults_apply_in_order() {
196        let m = Mem::default();
197        m.inject(Fault::LostResponse(Error::unavailable("lost", true)));
198        m.inject(Fault::Fail(Error::unavailable("down", true)));
199        assert!(block_on(m.put("a", Bytes::from_static(b"1"), Cond::CreateOnly)).is_err());
200        assert_eq!(block_on(m.get("a")).unwrap_err().to_string(), "store unavailable: down");
201        assert!(block_on(m.get("a")).unwrap().is_some(), "the lost-response write was applied");
202        assert!(matches!(
203            block_on(m.put("a", Bytes::new(), Cond::CreateOnly)),
204            Err(Error::Conflict)
205        ));
206        assert_eq!(m.ops(), 4);
207    }
208}