1use std::{
24 collections::HashMap,
25 sync::{Arc, Mutex, OnceLock},
26};
27
28use crate::layout::Range;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub struct HostBuffer {
33 pub base: usize,
35 pub len: usize,
37}
38
39impl HostBuffer {
40 pub fn address(&self, range: Range) -> usize {
42 assert!(
43 range
44 .offset
45 .checked_add(range.size)
46 .is_some_and(|end| end <= self.len),
47 "{range:?} is outside the {}-byte Host pool",
48 self.len
49 );
50 self.base + range.offset
51 }
52}
53
54#[derive(Debug, Clone, PartialEq, Eq)]
58pub struct IoResult {
59 pub success: Vec<bool>,
61 pub retired: bool,
63 pub object_failures: Vec<bool>,
66}
67
68#[derive(Debug, thiserror::Error)]
70#[error("{0}")]
71pub struct StoreError(pub String);
72
73pub trait Store: Send + Sync {
76 fn failures_retire(&self) -> bool;
79
80 fn batch_exists(&self, keys: &[String]) -> Result<Vec<bool>, StoreError>;
82
83 fn batch_get_into(
85 &self,
86 keys: &[String],
87 host: HostBuffer,
88 ranges: &[Range],
89 ) -> Result<IoResult, StoreError>;
90
91 fn batch_put_from(
93 &self,
94 keys: &[String],
95 host: HostBuffer,
96 ranges: &[Range],
97 ) -> Result<IoResult, StoreError>;
98
99 fn remove_by_prefix(&self, prefix: &str) -> bool;
102
103 fn close(&self);
106}
107
108impl<S: Store + ?Sized> Store for Arc<S> {
109 fn failures_retire(&self) -> bool {
110 (**self).failures_retire()
111 }
112
113 fn batch_exists(&self, keys: &[String]) -> Result<Vec<bool>, StoreError> {
114 (**self).batch_exists(keys)
115 }
116
117 fn batch_get_into(
118 &self,
119 keys: &[String],
120 host: HostBuffer,
121 ranges: &[Range],
122 ) -> Result<IoResult, StoreError> {
123 (**self).batch_get_into(keys, host, ranges)
124 }
125
126 fn batch_put_from(
127 &self,
128 keys: &[String],
129 host: HostBuffer,
130 ranges: &[Range],
131 ) -> Result<IoResult, StoreError> {
132 (**self).batch_put_from(keys, host, ranges)
133 }
134
135 fn remove_by_prefix(&self, prefix: &str) -> bool {
136 (**self).remove_by_prefix(prefix)
137 }
138
139 fn close(&self) {
140 (**self).close();
141 }
142}
143
144#[derive(Debug, Default)]
146pub struct MemoryStore {
147 objects: Mutex<HashMap<String, Vec<u8>>>,
148}
149
150pub fn process_memory_store() -> Arc<MemoryStore> {
153 static STORE: OnceLock<Arc<MemoryStore>> = OnceLock::new();
154 Arc::clone(STORE.get_or_init(Arc::default))
155}
156
157impl MemoryStore {
158 pub fn new() -> Self {
160 Self::default()
161 }
162
163 pub fn keys(&self) -> Vec<String> {
165 let mut keys: Vec<String> = self
166 .objects
167 .lock()
168 .expect("memory store lock")
169 .keys()
170 .cloned()
171 .collect();
172 keys.sort();
173 keys
174 }
175
176 pub fn forget(&self, forget: impl Fn(&str) -> bool) {
178 self.objects
179 .lock()
180 .expect("memory store lock")
181 .retain(|key, _| !forget(key));
182 }
183}
184
185impl Store for MemoryStore {
186 fn failures_retire(&self) -> bool {
187 true
188 }
189
190 fn batch_exists(&self, keys: &[String]) -> Result<Vec<bool>, StoreError> {
191 let objects = self.objects.lock().expect("memory store lock");
192 Ok(keys.iter().map(|key| objects.contains_key(key)).collect())
193 }
194
195 fn batch_get_into(
196 &self,
197 keys: &[String],
198 host: HostBuffer,
199 ranges: &[Range],
200 ) -> Result<IoResult, StoreError> {
201 if keys.len() != ranges.len() {
202 return Err(StoreError("ragged L3 get".to_owned()));
203 }
204 let objects = self.objects.lock().expect("memory store lock");
205 let success: Vec<bool> = keys
206 .iter()
207 .zip(ranges)
208 .map(|(key, &range)| match objects.get(key) {
209 Some(payload) if payload.len() == range.size => {
210 #[allow(unsafe_code)]
215 unsafe {
216 std::ptr::copy_nonoverlapping(
217 payload.as_ptr(),
218 host.address(range) as *mut u8,
219 range.size,
220 );
221 }
222 true
223 }
224 _ => false,
225 })
226 .collect();
227 let object_failures = success.iter().map(|ok| !ok).collect();
228 Ok(IoResult {
229 success,
230 retired: true,
231 object_failures,
232 })
233 }
234
235 fn batch_put_from(
236 &self,
237 keys: &[String],
238 host: HostBuffer,
239 ranges: &[Range],
240 ) -> Result<IoResult, StoreError> {
241 if keys.len() != ranges.len() {
242 return Err(StoreError("ragged L3 put".to_owned()));
243 }
244 let mut objects = self.objects.lock().expect("memory store lock");
245 for (key, &range) in keys.iter().zip(ranges) {
246 if objects.contains_key(key) {
247 continue;
248 }
249 let mut payload = vec![0_u8; range.size];
250 #[allow(unsafe_code)]
253 unsafe {
254 std::ptr::copy_nonoverlapping(
255 host.address(range) as *const u8,
256 payload.as_mut_ptr(),
257 range.size,
258 );
259 }
260 objects.insert(key.clone(), payload);
261 }
262 Ok(IoResult {
263 success: vec![true; keys.len()],
264 retired: true,
265 object_failures: vec![false; keys.len()],
266 })
267 }
268
269 fn remove_by_prefix(&self, prefix: &str) -> bool {
270 self.forget(|key| key.starts_with(prefix));
271 true
272 }
273
274 fn close(&self) {}
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280
281 #[test]
282 fn objects_round_trip_through_the_host_pool() {
283 let mut pool = vec![0_u8; 16];
284 pool[0..4].copy_from_slice(b"abcd");
285 let host = HostBuffer {
286 base: pool.as_mut_ptr() as usize,
287 len: pool.len(),
288 };
289 let store = MemoryStore::new();
290 let keys = vec!["k1".to_owned(), "k2".to_owned()];
291 let first = Range { offset: 0, size: 4 };
292 let put = store.batch_put_from(&keys[..1], host, &[first]).unwrap();
293 assert_eq!(put.success, [true]);
294 assert_eq!(store.batch_exists(&keys).unwrap(), [true, false]);
295
296 let target = Range { offset: 8, size: 4 };
297 let short = Range {
298 offset: 12,
299 size: 3,
300 };
301 let got = store
302 .batch_get_into(&["k1".to_owned(), "k1".to_owned()], host, &[target, short])
303 .unwrap();
304 assert_eq!(got.success, [true, false]);
306 assert_eq!(got.object_failures, [false, true]);
307 assert!(got.retired);
308 assert_eq!(&pool[8..12], b"abcd");
309
310 assert!(store.remove_by_prefix("k"));
311 assert!(store.keys().is_empty());
312 }
313}