1use planarian_schema::TxnId;
10use std::collections::BTreeMap;
11use std::io;
12use std::sync::{Arc, Condvar, Mutex, MutexGuard};
13
14pub const DEFAULT_CAPACITY: u64 = 64 * 1024 * 1024;
15pub const SENTINEL_SIZE: usize = 4096;
16
17pub trait Store {
18 type Guard;
21 fn lock(&self) -> io::Result<Self::Guard>;
22 fn capacity(&self) -> u64;
23 fn read_at(&self, off: u64, len: usize) -> io::Result<Vec<u8>>;
25 fn write_at(&self, off: u64, bytes: &[u8]) -> io::Result<()>;
26 fn sync(&self) -> io::Result<()>;
27 fn read_checkpoint(&self) -> io::Result<Option<Vec<u8>>>;
28 fn write_checkpoint(&self, bytes: &[u8]) -> io::Result<()>;
30 fn read_sentinel(&self) -> io::Result<Option<Vec<u8>>>;
31 fn write_sentinel(&self, bytes: &[u8]) -> io::Result<()>;
32 fn fence(&self, txn: TxnId) -> io::Result<Option<u64>>;
34 fn set_fence(&self, txn: TxnId, epoch: u64) -> io::Result<()>;
35 fn create_fence(&self, txn: TxnId, epoch: u64) -> io::Result<()>;
38 fn forget_fences<F: Fn(TxnId) -> bool>(&self, keep: F) -> io::Result<Vec<TxnId>>;
41}
42
43#[derive(Debug, Default)]
46struct MemInner {
47 log: Vec<u8>,
48 checkpoint: Option<Vec<u8>>,
49 sentinel: Option<Vec<u8>>,
50 fences: BTreeMap<TxnId, u64>,
51 tear_next_write: Option<usize>,
53 tear_write_containing: Option<Vec<u8>>,
56 torn: bool,
57 fail_next_checkpoint: bool,
59 enospc: bool,
61 unsynced: Vec<(usize, Vec<u8>)>,
67}
68
69#[derive(Clone, Debug)]
70pub struct MemStore {
71 inner: Arc<Mutex<MemInner>>,
72 lock: Arc<(Mutex<bool>, Condvar)>,
73 capacity: u64,
74}
75
76#[derive(Debug)]
78pub struct MemGuard(Arc<(Mutex<bool>, Condvar)>);
79
80impl Drop for MemGuard {
81 fn drop(&mut self) {
82 let (m, cv) = &*self.0;
83 *m.lock().unwrap_or_else(|e| e.into_inner()) = false;
84 cv.notify_one();
85 }
86}
87
88impl MemStore {
89 pub fn new(capacity: u64) -> MemStore {
90 MemStore {
91 inner: Arc::new(Mutex::new(MemInner { log: vec![0; capacity as usize], ..Default::default() })),
92 lock: Arc::new((Mutex::new(false), Condvar::new())),
93 capacity,
94 }
95 }
96
97 fn inner(&self) -> MutexGuard<'_, MemInner> {
98 self.inner.lock().unwrap_or_else(|e| e.into_inner())
99 }
100
101 pub fn crash(&self) {
104 let mut inner = self.inner();
105 while let Some((off, before)) = inner.unsynced.pop() {
106 inner.log[off..off + before.len()].copy_from_slice(&before);
107 }
108 }
109
110 pub fn tear_next_write(&self, keep: usize) {
111 self.inner().tear_next_write = Some(keep);
112 }
113
114 pub fn tear_write_containing(&self, needle: &str) {
117 self.inner().tear_write_containing = Some(needle.as_bytes().to_vec());
118 }
119
120 pub fn torn(&self) -> bool {
122 self.inner().torn
123 }
124
125 pub fn fail_next_checkpoint(&self) {
126 self.inner().fail_next_checkpoint = true;
127 }
128
129 pub fn set_enospc(&self, on: bool) {
130 self.inner().enospc = on;
131 }
132}
133
134impl Store for MemStore {
135 type Guard = MemGuard;
136
137 fn lock(&self) -> io::Result<MemGuard> {
138 let (m, cv) = &*self.lock;
139 let mut held = m.lock().unwrap_or_else(|e| e.into_inner());
140 while *held {
141 held = cv.wait(held).unwrap_or_else(|e| e.into_inner());
142 }
143 *held = true;
144 Ok(MemGuard(self.lock.clone()))
145 }
146
147 fn capacity(&self) -> u64 {
148 self.capacity
149 }
150
151 fn read_at(&self, off: u64, len: usize) -> io::Result<Vec<u8>> {
152 let inner = self.inner();
153 let start = (off as usize).min(inner.log.len());
154 let end = start.saturating_add(len).min(inner.log.len());
155 Ok(inner.log[start..end].to_vec())
156 }
157
158 fn write_at(&self, off: u64, bytes: &[u8]) -> io::Result<()> {
159 let mut inner = self.inner();
160 if inner.enospc {
161 return Err(io::Error::from_raw_os_error(28));
162 }
163 let off = off as usize;
164 if off + bytes.len() > inner.log.len() {
165 return Err(io::Error::from_raw_os_error(28));
166 }
167 let matches = inner.tear_write_containing.as_ref().is_some_and(|n| bytes.windows(n.len()).any(|w| w == n));
168 if matches {
169 inner.tear_write_containing = None;
170 inner.tear_next_write = Some(bytes.len() / 2);
171 }
172 if let Some(keep) = inner.tear_next_write.take() {
173 inner.torn = true;
174 let keep = keep.min(bytes.len());
175 let before = inner.log[off..off + keep].to_vec();
176 inner.unsynced.push((off, before));
177 inner.log[off..off + keep].copy_from_slice(&bytes[..keep]);
178 return Err(io::Error::other("torn write (injected)"));
179 }
180 let before = inner.log[off..off + bytes.len()].to_vec();
181 inner.unsynced.push((off, before));
182 inner.log[off..off + bytes.len()].copy_from_slice(bytes);
183 Ok(())
184 }
185
186 fn sync(&self) -> io::Result<()> {
187 self.inner().unsynced.clear();
188 Ok(())
189 }
190
191 fn read_checkpoint(&self) -> io::Result<Option<Vec<u8>>> {
192 Ok(self.inner().checkpoint.clone())
193 }
194
195 fn write_checkpoint(&self, bytes: &[u8]) -> io::Result<()> {
196 let mut inner = self.inner();
197 if std::mem::take(&mut inner.fail_next_checkpoint) {
198 return Err(io::Error::other("interrupted checkpoint write (injected)"));
199 }
200 inner.checkpoint = Some(bytes.to_vec());
201 Ok(())
202 }
203
204 fn read_sentinel(&self) -> io::Result<Option<Vec<u8>>> {
205 Ok(self.inner().sentinel.clone())
206 }
207
208 fn write_sentinel(&self, bytes: &[u8]) -> io::Result<()> {
209 let mut s = vec![0; SENTINEL_SIZE];
210 let n = bytes.len().min(SENTINEL_SIZE);
211 s[..n].copy_from_slice(&bytes[..n]);
212 self.inner().sentinel = Some(s);
213 Ok(())
214 }
215
216 fn fence(&self, txn: TxnId) -> io::Result<Option<u64>> {
217 Ok(self.inner().fences.get(&txn).copied())
218 }
219
220 fn set_fence(&self, txn: TxnId, epoch: u64) -> io::Result<()> {
221 self.inner().fences.insert(txn, epoch);
222 Ok(())
223 }
224
225 fn create_fence(&self, txn: TxnId, epoch: u64) -> io::Result<()> {
226 let mut i = self.inner();
227 if i.fences.contains_key(&txn) {
228 return Err(io::Error::new(io::ErrorKind::AlreadyExists, format!("txn/{} exists", txn.0)));
229 }
230 i.fences.insert(txn, epoch);
231 Ok(())
232 }
233
234 fn forget_fences<F: Fn(TxnId) -> bool>(&self, keep: F) -> io::Result<Vec<TxnId>> {
235 let mut i = self.inner();
236 let gone: Vec<TxnId> = i.fences.keys().copied().filter(|t| !keep(*t)).collect();
237 for t in &gone {
238 i.fences.remove(t);
239 }
240 Ok(gone)
241 }
242}
243
244#[derive(Debug)]
252pub struct FileStore {
253 dir: std::path::PathBuf,
254 txn_dir: std::path::PathBuf,
255 journal: std::fs::File,
256 capacity: u64,
257}
258
259impl FileStore {
260 pub fn open(dir: &std::path::Path, txn_dir: &std::path::Path, capacity: u64) -> io::Result<FileStore> {
261 use std::fs::{File, OpenOptions};
262 std::fs::create_dir_all(dir)?;
263 std::fs::create_dir_all(txn_dir)?;
264 let path = dir.join("journal");
265 let fresh = !path.exists();
266 let journal = OpenOptions::new().read(true).write(true).create(true).truncate(false).open(&path)?;
267 if fresh || journal.metadata()?.len() < capacity {
268 rustix::fs::fallocate(&journal, rustix::fs::FallocateFlags::empty(), 0, capacity)?;
269 journal.sync_all()?;
270 File::open(dir)?.sync_all()?;
271 }
272 let sentinel = dir.join("sentinel");
273 if !sentinel.exists() {
274 let f = File::create(&sentinel)?;
275 rustix::fs::fallocate(&f, rustix::fs::FallocateFlags::empty(), 0, SENTINEL_SIZE as u64)?;
276 f.sync_all()?;
277 }
278 Ok(FileStore { dir: dir.to_owned(), txn_dir: txn_dir.to_owned(), journal, capacity })
279 }
280
281 fn sync_txn_dir(&self) -> io::Result<()> {
288 std::fs::File::open(&self.txn_dir)?.sync_all()
289 }
290
291 fn replace_atomically(&self, target: &std::path::Path, bytes: &[u8]) -> io::Result<()> {
292 use std::io::Write;
293 let parent = target.parent().expect("target has a parent");
294 let tmp =
295 parent.join(format!(".{}.tmp", target.file_name().expect("target has a file name").to_string_lossy()));
296 let mut f = std::fs::File::create(&tmp)?;
297 f.write_all(bytes)?;
298 f.sync_all()?;
299 std::fs::rename(&tmp, target)?;
300 std::fs::File::open(parent)?.sync_all()
301 }
302}
303
304impl Store for FileStore {
305 type Guard = std::fs::File;
307
308 fn lock(&self) -> io::Result<std::fs::File> {
309 let lock = std::fs::OpenOptions::new().create(true).truncate(false).write(true).open(self.dir.join("lock"))?;
310 rustix::fs::flock(&lock, rustix::fs::FlockOperation::LockExclusive)?;
311 Ok(lock)
312 }
313
314 fn capacity(&self) -> u64 {
315 self.capacity
316 }
317
318 fn read_at(&self, off: u64, len: usize) -> io::Result<Vec<u8>> {
319 use std::os::unix::fs::FileExt;
320 let end = off.saturating_add(len as u64).min(self.capacity);
321 let mut buf = vec![0; end.saturating_sub(off) as usize];
322 self.journal.read_exact_at(&mut buf, off)?;
323 Ok(buf)
324 }
325
326 fn write_at(&self, off: u64, bytes: &[u8]) -> io::Result<()> {
327 use std::os::unix::fs::FileExt;
328 if off + bytes.len() as u64 > self.capacity {
329 return Err(io::Error::from_raw_os_error(28));
330 }
331 self.journal.write_all_at(bytes, off)
332 }
333
334 fn sync(&self) -> io::Result<()> {
335 self.journal.sync_data()
336 }
337
338 fn read_checkpoint(&self) -> io::Result<Option<Vec<u8>>> {
339 match std::fs::read(self.dir.join("checkpoint")) {
340 Ok(b) => Ok(Some(b)),
341 Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(None),
342 Err(e) => Err(e),
343 }
344 }
345
346 fn write_checkpoint(&self, bytes: &[u8]) -> io::Result<()> {
347 self.replace_atomically(&self.dir.join("checkpoint"), bytes)
348 }
349
350 fn read_sentinel(&self) -> io::Result<Option<Vec<u8>>> {
351 let b = std::fs::read(self.dir.join("sentinel"))?;
352 Ok(if b.iter().all(|x| *x == 0) { None } else { Some(b) })
353 }
354
355 fn write_sentinel(&self, bytes: &[u8]) -> io::Result<()> {
356 use std::os::unix::fs::FileExt;
357 let f = std::fs::OpenOptions::new().write(true).open(self.dir.join("sentinel"))?;
359 let n = bytes.len().min(SENTINEL_SIZE);
360 f.write_all_at(&bytes[..n], 0)?;
361 f.sync_data()
362 }
363
364 fn fence(&self, txn: TxnId) -> io::Result<Option<u64>> {
365 match std::fs::read_to_string(self.txn_dir.join(txn.0.to_string()).join("lease")) {
366 Ok(s) => Ok(s.trim().parse().ok()),
367 Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(None),
368 Err(e) => Err(e),
369 }
370 }
371
372 fn set_fence(&self, txn: TxnId, epoch: u64) -> io::Result<()> {
373 let d = self.txn_dir.join(txn.0.to_string());
374 if !d.is_dir() {
375 std::fs::create_dir_all(&d)?;
376 self.sync_txn_dir()?;
377 }
378 self.replace_atomically(&d.join("lease"), format!("{epoch}\n").as_bytes())
379 }
380
381 fn create_fence(&self, txn: TxnId, epoch: u64) -> io::Result<()> {
382 std::fs::create_dir_all(&self.txn_dir)?;
383 let d = self.txn_dir.join(txn.0.to_string());
384 std::fs::create_dir(&d)?;
385 self.sync_txn_dir()?;
386 self.replace_atomically(&d.join("lease"), format!("{epoch}\n").as_bytes())
387 }
388
389 fn forget_fences<F: Fn(TxnId) -> bool>(&self, keep: F) -> io::Result<Vec<TxnId>> {
390 let entries = match std::fs::read_dir(&self.txn_dir) {
391 Ok(e) => e,
392 Err(e) if e.kind() == io::ErrorKind::NotFound => return Ok(vec![]),
393 Err(e) => return Err(e),
394 };
395 let mut gone = vec![];
396 for e in entries {
397 let e = e?;
398 let Some(id) = e.file_name().to_str().and_then(|n| n.parse::<u64>().ok()).map(TxnId) else {
400 continue;
401 };
402 if !keep(id) {
403 std::fs::remove_dir_all(e.path())?;
404 gone.push(id);
405 }
406 }
407 if !gone.is_empty() {
408 std::fs::File::open(&self.txn_dir)?.sync_all()?;
409 }
410 gone.sort();
411 Ok(gone)
412 }
413}