Skip to main content

objstore_r/
mem.rs

1// Author: spicyzhug@gmail.com
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//!
11//! Encoded objects ([`ObjectStore::put_encoded`]) keep their encoding beside
12//! the stored bytes and are decoded on `get`, outside the lock.
13
14use crate::encoding::{self, ContentEncoding, MAX_INFLATED_BYTES};
15use crate::key::check_prefix;
16use crate::{clip, Bytes, Cond, Error, Key, Meta, Object, ObjectStore, Result, Stat};
17use std::collections::{BTreeMap, VecDeque};
18use std::future::{ready, Ready};
19use std::ops::Range;
20use std::sync::Mutex;
21
22/// A scripted outcome for one future operation.
23#[derive(Debug)]
24#[non_exhaustive]
25pub enum Fault {
26    /// Let the operation through untouched (a placeholder that lets a real
27    /// fault target the n-th operation; see [`Mem::inject_after`]).
28    Pass,
29    /// Fail without applying the operation.
30    Fail(Error),
31    /// Apply the operation, then report this error.
32    LostResponse(Error),
33}
34
35/// The stored bytes and how they are encoded.
36#[derive(Debug, Clone)]
37struct Stored {
38    obj: Object,
39    encoding: Option<ContentEncoding>,
40}
41
42impl Stored {
43    fn meta(&self, key: String) -> Meta {
44        Meta { key, size: self.obj.bytes.len() as u64, generation: self.obj.generation }
45    }
46}
47
48#[derive(Debug, Default)]
49struct Inner {
50    objects: BTreeMap<String, Stored>,
51    next_generation: u64,
52    faults: VecDeque<Fault>,
53    ops: u64,
54}
55
56/// An in-memory [`ObjectStore`]. Generations increase monotonically across
57/// the whole store, like GCS.
58#[derive(Debug)]
59pub struct Mem {
60    inner: Mutex<Inner>,
61    /// Bound on a decoded body.
62    max_inflated: u64,
63}
64
65impl Default for Mem {
66    fn default() -> Self {
67        Self { inner: Mutex::default(), max_inflated: MAX_INFLATED_BYTES }
68    }
69}
70
71impl Mem {
72    /// Refuse to decode an encoded object past `limit` bytes
73    /// ([`Error::TooLarge`]); the default is [`MAX_INFLATED_BYTES`].
74    #[must_use]
75    pub fn with_max_inflated(mut self, limit: u64) -> Self {
76        self.max_inflated = limit;
77        self
78    }
79
80    /// Queue a fault for a future operation (consumed in order, one per call).
81    pub fn inject(&self, fault: Fault) {
82        self.lock().faults.push_back(fault);
83    }
84
85    /// Let `skip` operations through, then apply `fault` — for crash tests
86    /// that fail each step of a multi-operation protocol in turn.
87    pub fn inject_after(&self, skip: u64, fault: Fault) {
88        let mut g = self.lock();
89        for _ in 0..skip {
90            g.faults.push_back(Fault::Pass);
91        }
92        g.faults.push_back(fault);
93    }
94
95    /// Drop every queued fault.
96    pub fn clear_faults(&self) {
97        self.lock().faults.clear();
98    }
99
100    /// Operations served so far (every call, failed or not).
101    #[must_use]
102    pub fn ops(&self) -> u64 {
103        self.lock().ops
104    }
105
106    /// Every key, sorted.
107    #[must_use]
108    pub fn keys(&self) -> Vec<String> {
109        self.lock().objects.keys().cloned().collect()
110    }
111
112    fn lock(&self) -> std::sync::MutexGuard<'_, Inner> {
113        // A panic while holding the lock can only come from a bug in this
114        // module; the data is still consistent (every mutation is one insert
115        // or remove), so poisoning is ignored.
116        self.inner.lock().unwrap_or_else(std::sync::PoisonError::into_inner)
117    }
118
119    fn put_stored(
120        &self,
121        key: &str,
122        body: Bytes,
123        cond: Cond,
124        encoding: Option<ContentEncoding>,
125    ) -> Result<u64> {
126        let k = Key::parse(key)?;
127        self.run(|g| {
128            let current = g.objects.get(k.as_str()).map(|o| o.obj.generation);
129            match (cond, current) {
130                (Cond::CreateOnly, Some(_)) => return Err(Error::Conflict),
131                (Cond::IfGeneration(want), cur) if cur != Some(want) => {
132                    return Err(Error::PreconditionFailed)
133                }
134                _ => {}
135            }
136            g.next_generation += 1;
137            let generation = g.next_generation;
138            let obj = Object { bytes: body, generation };
139            g.objects.insert(k.as_str().to_owned(), Stored { obj, encoding });
140            Ok(generation)
141        })
142    }
143
144    fn run<T>(&self, op: impl FnOnce(&mut Inner) -> Result<T>) -> Result<T> {
145        let mut g = self.lock();
146        g.ops += 1;
147        match g.faults.pop_front() {
148            Some(Fault::Fail(e)) => Err(e),
149            Some(Fault::LostResponse(e)) => {
150                let _ = op(&mut g);
151                Err(e)
152            }
153            Some(Fault::Pass) | None => op(&mut g),
154        }
155    }
156}
157
158impl ObjectStore for Mem {
159    type Get<'a> = Ready<Result<Option<Object>>>;
160    type GetRange<'a> = Ready<Result<Bytes>>;
161    type Head<'a> = Ready<Result<Option<Meta>>>;
162    type Put<'a> = Ready<Result<u64>>;
163    type List<'a> = Ready<Result<Vec<Meta>>>;
164    type Delete<'a> = Ready<Result<()>>;
165
166    fn get<'a>(&'a self, key: &'a str) -> Self::Get<'a> {
167        let stored =
168            Key::parse(key).and_then(|k| self.run(|g| Ok(g.objects.get(k.as_str()).cloned())));
169        ready(stored.and_then(|s| {
170            s.map(|Stored { obj, encoding }| match encoding {
171                None => Ok(obj),
172                Some(e) => Ok(Object {
173                    bytes: encoding::decode(e, &obj.bytes, self.max_inflated)?,
174                    generation: obj.generation,
175                }),
176            })
177            .transpose()
178        }))
179    }
180
181    fn get_range<'a>(&'a self, key: &'a str, range: Range<u64>) -> Self::GetRange<'a> {
182        ready(Key::parse(key).and_then(|k| {
183            self.run(|g| {
184                let o = g.objects.get(k.as_str()).ok_or(Error::NotFound)?;
185                if let Some(e) = o.encoding {
186                    return Err(Error::EncodedRange { encoding: e.as_str().into() });
187                }
188                Ok(o.obj.bytes.slice(clip(&range, o.obj.bytes.len() as u64)))
189            })
190        }))
191    }
192
193    fn head<'a>(&'a self, key: &'a str) -> Self::Head<'a> {
194        ready(Key::parse(key).and_then(|k| {
195            self.run(|g| Ok(g.objects.get(k.as_str()).map(|o| o.meta(k.as_str().to_owned()))))
196        }))
197    }
198
199    fn put<'a>(&'a self, key: &'a str, body: Bytes, cond: Cond) -> Self::Put<'a> {
200        ready(self.put_stored(key, body, cond, None))
201    }
202
203    fn list<'a>(&'a self, prefix: &'a str) -> Self::List<'a> {
204        ready(check_prefix(prefix).and_then(|()| {
205            self.run(|g| {
206                Ok(g.objects
207                    .range(prefix.to_owned()..)
208                    .take_while(|(k, _)| k.starts_with(prefix))
209                    .map(|(k, o)| o.meta(k.clone()))
210                    .collect())
211            })
212        }))
213    }
214
215    fn delete<'a>(&'a self, key: &'a str) -> Self::Delete<'a> {
216        ready(Key::parse(key).and_then(|k| {
217            self.run(|g| {
218                g.objects.remove(k.as_str());
219                Ok(())
220            })
221        }))
222    }
223
224    fn put_encoded<'a>(
225        &'a self,
226        key: &'a str,
227        body: Bytes,
228        cond: Cond,
229        encoding: ContentEncoding,
230    ) -> impl std::future::Future<Output = Result<u64>> + 'a {
231        // Validate before compressing; compress outside the lock.
232        ready(Key::parse(key).and_then(|_| {
233            let stored = encoding::encode(encoding, &body)?;
234            self.put_stored(key, stored, cond, Some(encoding))
235        }))
236    }
237
238    fn stat<'a>(
239        &'a self,
240        key: &'a str,
241    ) -> impl std::future::Future<Output = Result<Option<Stat>>> + 'a {
242        ready(Key::parse(key).and_then(|k| {
243            self.run(|g| {
244                Ok(g.objects.get(k.as_str()).map(|o| Stat {
245                    meta: o.meta(k.as_str().to_owned()),
246                    content_encoding: o.encoding,
247                }))
248            })
249        }))
250    }
251
252    fn list_stat<'a>(
253        &'a self,
254        prefix: &'a str,
255    ) -> impl std::future::Future<Output = Result<Vec<Stat>>> + 'a {
256        ready(check_prefix(prefix).and_then(|()| {
257            self.run(|g| {
258                Ok(g.objects
259                    .range(prefix.to_owned()..)
260                    .take_while(|(k, _)| k.starts_with(prefix))
261                    .map(|(k, o)| Stat { meta: o.meta(k.clone()), content_encoding: o.encoding })
262                    .collect())
263            })
264        }))
265    }
266}
267
268#[cfg(test)]
269mod tests {
270    use super::*;
271
272    #[test]
273    fn inject_after_targets_the_nth_operation() {
274        let m = Mem::default();
275        m.inject_after(2, Fault::Fail(Error::unavailable("boom", true)));
276        let put = |k| futures::executor::block_on(m.put(k, Bytes::new(), Cond::None));
277        assert!(put("a").is_ok());
278        assert!(put("b").is_ok());
279        assert!(put("c").is_err());
280        assert!(put("d").is_ok());
281    }
282    use futures::executor::block_on;
283
284    #[test]
285    fn faults_apply_in_order() {
286        let m = Mem::default();
287        m.inject(Fault::LostResponse(Error::unavailable("lost", true)));
288        m.inject(Fault::Fail(Error::unavailable("down", true)));
289        assert!(block_on(m.put("a", Bytes::from_static(b"1"), Cond::CreateOnly)).is_err());
290        assert_eq!(block_on(m.get("a")).unwrap_err().to_string(), "store unavailable: down");
291        assert!(block_on(m.get("a")).unwrap().is_some(), "the lost-response write was applied");
292        assert!(matches!(
293            block_on(m.put("a", Bytes::new(), Cond::CreateOnly)),
294            Err(Error::Conflict)
295        ));
296        assert_eq!(m.ops(), 4);
297    }
298}