1use crate::disk::SimDisk;
4use crate::rng::Rng;
5use fusee::env::{Env, Notify};
6use fusee_core::Lsn;
7use fusee_core::segment::Batch;
8use fusee_core::store::{Checkpoint, store_header};
9use std::cell::RefCell;
10use std::collections::BTreeMap;
11use std::io;
12use std::rc::Rc;
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
16pub enum Io {
17 Flush {
18 shard: u32,
19 epoch: u64,
20 },
21 Checkpoint {
22 shard: u32,
23 epoch: u64,
24 },
25 LogFlush {
27 epoch: u64,
28 seq: u64,
29 },
30 BlobFlush {
32 epoch: u64,
33 id: u64,
34 },
35}
36
37pub struct SimDevice {
41 m: MachineRef,
42 prefix: &'static str,
43 touched: Rc<RefCell<Vec<(u64, u64, u64)>>>,
46}
47
48impl SimDevice {
49 fn blobs(m: &MachineRef) -> Self {
50 Self {
51 m: m.clone(),
52 prefix: "blob/",
53 touched: Rc::new(RefCell::new(Vec::new())),
54 }
55 }
56
57 fn name(&self, number: u64) -> String {
58 format!("{}{number:020}", self.prefix)
59 }
60}
61
62impl LogDevice for SimDevice {
63 fn segments(&self) -> io::Result<Vec<u64>> {
64 let m = self.m.borrow();
65 let mut v: Vec<u64> = m
66 .disk
67 .names_with_prefix(self.prefix)
68 .iter()
69 .filter_map(|n| n.strip_prefix(self.prefix)?.parse().ok())
70 .collect();
71 v.sort_unstable();
72 Ok(v)
73 }
74 fn len(&self, segment: u64) -> io::Result<u64> {
75 let m = self.m.borrow();
76 let f = m
77 .disk
78 .get(&self.name(segment))
79 .ok_or_else(|| io::Error::other("missing log segment"))?;
80 Ok(f.view().len() as u64)
81 }
82 fn read(&self, segment: u64, offset: u64, len: usize) -> io::Result<Vec<u8>> {
83 let m = self.m.borrow();
84 let img = m
85 .disk
86 .get(&self.name(segment))
87 .ok_or_else(|| io::Error::other("missing log segment"))?
88 .view();
89 let start = (offset as usize).min(img.len());
90 let end = start.saturating_add(len).min(img.len());
91 Ok(img[start..end].to_vec())
92 }
93 fn create(&self, segment: u64, len: u64) -> io::Result<()> {
94 let mut m = self.m.borrow_mut();
95 m.disk.remove(&self.name(segment));
96 let f = m.disk.file(&self.name(segment));
97 f.write(0, &vec![0; len as usize]);
98 f.flush();
99 Ok(())
100 }
101 fn write(&self, segment: u64, offset: u64, bytes: &[u8]) -> io::Result<()> {
102 let block = fusee_core::wal::BLOCK;
103 let last = (offset + (bytes.len() as u64).max(1) - 1) / block;
104 if self.prefix == "wal/" {
105 self.touched
106 .borrow_mut()
107 .push((segment, offset / block, last));
108 }
109 self.m
110 .borrow_mut()
111 .disk
112 .file(&self.name(segment))
113 .write(offset, bytes);
114 Ok(())
115 }
116 fn sync(&self, segment: u64) -> io::Result<()> {
117 self.m.borrow_mut().disk.file(&self.name(segment)).flush();
118 Ok(())
119 }
120 fn rename(&self, from: u64, to: u64) -> io::Result<()> {
121 let mut m = self.m.borrow_mut();
122 let f = m
123 .disk
124 .files
125 .remove(&self.name(from))
126 .ok_or_else(|| io::Error::other("missing log segment"))?;
127 m.disk.files.insert(self.name(to), f);
128 Ok(())
129 }
130 fn remove(&self, segment: u64) -> io::Result<()> {
131 self.m.borrow_mut().disk.remove(&self.name(segment));
132 Ok(())
133 }
134}
135
136#[derive(Clone, Copy, Debug, Default)]
138pub struct Offload {
139 pub threshold: u64,
142 pub early: bool,
145}
146
147pub struct SimLog {
150 pub wal: Wal<SimDevice>,
151 blob_files: Vec<u64>,
153 offload: Offload,
154 in_flight: Vec<InFlight>,
155 failed: bool,
156 epoch: u64,
157 touched: Rc<RefCell<Vec<(u64, u64, u64)>>>,
158 blocks: Vec<(u64, u64, u64, u64)>,
160 violation: Option<String>,
164}
165
166pub type SimLogRef = Rc<RefCell<SimLog>>;
167
168impl SimLog {
169 pub fn open(
170 m: &MachineRef,
171 shards: u32,
172 segment_bytes: u64,
173 in_flight: usize,
174 spares: usize,
175 offload: Offload,
176 ) -> io::Result<SimLogRef> {
177 let (now, life, epoch) = {
178 let mut mm = m.borrow_mut();
179 (mm.now, mm.rng.next() | 1, mm.epoch)
180 };
181 let opts = WalOptions {
182 segment_bytes,
183 first_segment_bytes: (segment_bytes / 4).max(16 << 10),
184 in_flight,
185 spares,
186 ..WalOptions::default()
187 };
188 let touched = Rc::new(RefCell::new(Vec::new()));
189 let dev = SimDevice {
190 m: m.clone(),
191 prefix: "wal/",
192 touched: touched.clone(),
193 };
194 let wal = Wal::open_with_blobs(
195 dev,
196 Blobs {
197 dev: SimDevice::blobs(m),
198 threshold: offload.threshold,
199 },
200 shards,
201 opts,
202 life,
203 now,
204 )?;
205 touched.borrow_mut().clear();
206 Ok(Rc::new(RefCell::new(Self {
207 wal,
208 blob_files: Vec::new(),
209 offload,
210 in_flight: Vec::new(),
211 failed: false,
212 epoch,
213 touched,
214 blocks: Vec::new(),
215 violation: None,
216 })))
217 }
218
219 pub fn violation(&self) -> Option<&str> {
221 self.violation.as_deref()
222 }
223
224 fn start_blobs(&mut self, m: &MachineRef) {
228 if self.failed {
229 return;
230 }
231 let jobs = self.wal.take_blob_jobs();
232 let Some(last) = jobs.last().map(|j| j.id) else {
233 return;
234 };
235 let Some(dev) = self.wal.blob_device() else {
236 return;
237 };
238 for j in &jobs {
239 if j.perform(dev).is_err() {
240 self.failed = true;
241 return;
242 }
243 if !self.blob_files.contains(&j.file) {
244 self.blob_files.push(j.file);
245 }
246 }
247 if self.offload.early && self.wal.blobs_written(last).is_err() {
248 self.failed = true;
249 }
250 let mut mm = m.borrow_mut();
251 let after = mm.latency();
252 let epoch = self.epoch;
253 mm.schedule(after, Io::BlobFlush { epoch, id: last });
254 }
255
256 pub fn complete_blobs(log: &SimLogRef, m: &MachineRef, id: u64) {
258 let mut l = log.borrow_mut();
259 let fail = {
260 let mut mm = m.borrow_mut();
261 let p = mm.flush_fail_per_mille;
262 let f = mm.rng.chance(p);
263 if f {
264 mm.log.push("blob area flush failed".to_string());
265 }
266 f
267 };
268 if fail || l.failed {
269 l.failed = true;
270 return;
271 }
272 let files = std::mem::take(&mut l.blob_files);
273 let synced = match l.wal.blob_device() {
274 Some(dev) => files.iter().try_for_each(|f| dev.sync(*f)),
275 None => Ok(()),
276 };
277 if synced.and_then(|()| l.wal.blobs_written(id)).is_err() {
278 l.failed = true;
279 return;
280 }
281 l.start(m);
282 }
283
284 fn start(&mut self, m: &MachineRef) {
287 self.start_blobs(m);
288 while !self.failed && self.wal.has_staged() && self.wal.can_begin() {
289 let (now, overlap) = {
293 let mut mm = m.borrow_mut();
294 let packed = self.in_flight.is_empty() && mm.rng.chance(500);
295 (mm.now, !packed)
296 };
297 match self.wal.begin_write(now, overlap) {
298 Ok(Some(w)) => {
299 let seq = w.seq();
300 let touched: Vec<_> = self.touched.borrow_mut().drain(..).collect();
301 for (segment, first, last) in touched {
302 if let Some(other) = self
303 .blocks
304 .iter()
305 .find(|b| b.0 != seq && b.1 == segment && b.2 <= last && b.3 >= first)
306 && self.violation.is_none()
307 {
308 self.violation = Some(format!(
309 "log writes {} and {seq} were in flight together and both \
310 touched segment {segment} block {}",
311 other.0,
312 first.max(other.2)
313 ));
314 }
315 self.blocks.push((seq, segment, first, last));
316 }
317 self.in_flight.push(w);
318 let mut mm = m.borrow_mut();
319 let after = mm.latency();
320 let epoch = self.epoch;
321 mm.schedule(after, Io::LogFlush { epoch, seq });
322 }
323 Ok(None) => break,
324 Err(_) => self.failed = true,
325 }
326 }
327 }
328
329 pub fn in_flight(&self) -> usize {
331 self.in_flight.len()
332 }
333
334 pub fn complete(log: &SimLogRef, m: &MachineRef, seq: u64) {
336 let mut l = log.borrow_mut();
337 let Some(i) = l.in_flight.iter().position(|w| w.seq() == seq) else {
338 return;
339 };
340 let w = l.in_flight.remove(i);
341 l.blocks.retain(|b| b.0 != seq);
342 let fail = {
343 let mut mm = m.borrow_mut();
344 let p = mm.flush_fail_per_mille;
345 let f = mm.rng.chance(p);
346 if f {
347 mm.log.push("shared log flush failed".to_string());
348 }
349 f
350 };
351 if fail || l.failed || l.wal.finish_write(w).is_err() {
352 l.failed = true;
354 return;
355 }
356 l.start(m);
357 }
358}
359
360use fusee::wal::{Blobs, InFlight, LogDevice, Wal, WalOptions};
361
362pub struct Machine {
364 pub disk: SimDisk,
365 pub now: u64,
366 pub rng: Rng,
367 pub io: BTreeMap<(u64, u64), Io>,
369 pub io_seq: u64,
370 pub epoch: u64,
371 pub flush_fail_per_mille: u32,
372 pub max_io_ms: u64,
373 pub log: Vec<String>,
374}
375
376pub type MachineRef = Rc<RefCell<Machine>>;
377
378impl Machine {
379 pub fn schedule(&mut self, after: u64, io: Io) {
380 let t = self.now + after;
381 let s = self.io_seq;
382 self.io_seq += 1;
383 self.io.insert((t, s), io);
384 }
385
386 pub fn latency(&mut self) -> u64 {
387 let max = self.max_io_ms.max(1);
388 self.rng.below(max + 1)
389 }
390}
391
392pub struct SimEnv {
393 log: Option<SimLogRef>,
395 log_in_flight: Option<Lsn>,
396 log_max_in_flight: usize,
400 log_reported: Lsn,
401 log_checkpoints: Vec<Lsn>,
403 log_retained: Vec<Lsn>,
404 shard: u32,
405 epoch: u64,
406 m: MachineRef,
407 rng: Rng,
408 flushing: Option<Batch>,
409 flushed: Option<io::Result<Lsn>>,
410 ckpt: Option<(Checkpoint, Option<u64>)>,
411 ckpt_done: Option<io::Result<Lsn>>,
412 store_gen: u64,
413 store_len: u64,
414 indexed_prefix: Option<(u64, u64)>,
418 failed: bool,
419}
420
421fn seg_name(shard: u32, seg: u64) -> String {
422 format!("s{shard:03}/log/{seg:020}")
423}
424
425fn store_name(shard: u32, generation: u64) -> String {
426 format!("s{shard:03}/runs-{generation:010}")
427}
428
429fn ckpt_name(shard: u32, lsn: Lsn) -> String {
430 format!("s{shard:03}/cp-{lsn:020}")
431}
432
433impl SimEnv {
434 pub fn new(shard: u32, m: MachineRef) -> Self {
435 let (epoch, rng) = {
436 let mut mm = m.borrow_mut();
437 (mm.epoch, mm.rng.fork())
438 };
439 Self {
440 log: None,
441 log_in_flight: None,
442 log_max_in_flight: 1,
443 log_reported: 0,
444 log_checkpoints: Vec::new(),
445 log_retained: Vec::new(),
446 shard,
447 epoch,
448 m,
449 rng,
450 flushing: None,
451 flushed: None,
452 ckpt: None,
453 ckpt_done: None,
454 store_gen: 0,
455 store_len: 0,
456 indexed_prefix: None,
457 failed: false,
458 }
459 }
460
461 pub fn with_log(shard: u32, m: MachineRef, log: SimLogRef) -> Self {
463 let mut env = Self::new(shard, m);
464 env.log_max_in_flight = 1 + ((env.epoch + u64::from(shard)) % 2) as usize;
467 env.log_reported = log.borrow().wal.durable(shard);
468 env.log = Some(log);
469 env
470 }
471
472 fn report_retained(&self) -> io::Result<()> {
473 if let (Some(log), Some(min)) = (&self.log, self.log_retained.iter().copied().min()) {
474 log.borrow_mut().wal.set_retain(self.shard, min)?;
475 }
476 Ok(())
477 }
478
479 pub fn complete_flush(&mut self) {
481 let Some(batch) = self.flushing.take() else {
482 return;
483 };
484 let mut m = self.m.borrow_mut();
485 let fail = {
486 let p = m.flush_fail_per_mille;
487 m.rng.chance(p)
488 };
489 if fail || self.failed {
490 self.failed = true;
494 m.log.push(format!(
495 "shard {} flush failed (batch ..={})",
496 self.shard, batch.last_lsn
497 ));
498 self.flushed = Some(Err(io::Error::other("simulated fsync failure")));
499 return;
500 }
501 let mut names: Vec<String> = Vec::new();
502 for c in &batch.chunks {
503 let n = seg_name(self.shard, c.segment);
504 if !names.contains(&n) {
505 names.push(n);
506 }
507 }
508 for n in names {
509 m.disk.file(&n).flush();
510 }
511 self.flushed = Some(Ok(batch.last_lsn));
512 }
513
514 pub fn complete_checkpoint(&mut self) {
515 let Some((cp, delete)) = self.ckpt.take() else {
516 return;
517 };
518 let mut m = self.m.borrow_mut();
519 let shard = self.shard;
520 m.disk.file(&store_name(shard, cp.store_generation)).flush();
521 let bytes = cp.encode();
522 let name = ckpt_name(shard, cp.lsn);
523 let f = m.disk.file(&name);
524 f.truncate(0);
525 f.write(0, &bytes);
526 f.flush();
527 let mut cps = m.disk.names_with_prefix(&format!("s{shard:03}/cp-"));
528 cps.sort();
529 if cps.len() > 2 {
530 for old in &cps[..cps.len() - 2] {
531 m.disk.remove(old);
532 }
533 }
534 let saved: Vec<_> = cps
535 .iter()
536 .rev()
537 .take(2)
538 .filter_map(|name| m.disk.get(name))
539 .filter_map(|f| Checkpoint::decode(&f.view()).ok())
540 .collect();
541 for name in m.disk.names_with_prefix(&format!("s{shard:03}/runs-")) {
542 if !saved
543 .iter()
544 .any(|c| store_name(shard, c.store_generation) == name)
545 {
546 m.disk.remove(&name);
547 }
548 }
549 if self.log.is_some() {
550 if let Some(i) = self.log_checkpoints.iter().position(|l| *l == cp.lsn) {
551 self.log_checkpoints.remove(i);
552 self.log_retained.push(cp.lsn);
553 if self.log_retained.len() > 2 {
554 self.log_retained.remove(0);
555 }
556 }
557 drop(m);
558 self.ckpt_done = Some(self.report_retained().map(|()| cp.lsn));
559 return;
560 }
561 if let (Some(through), Some(oldest)) = (delete, saved.last()) {
562 let mut segments = m.disk.names_with_prefix(&format!("s{shard:03}/log/"));
563 segments.sort();
564 let boundary = segments.iter().rev().find_map(|path| {
565 let number = path.rsplit('/').next()?.parse::<u64>().ok()?;
566 if number > through.saturating_add(1) {
567 return None;
568 }
569 let bytes = m.disk.get(path)?.read(0, 44)?;
570 let header = fusee_core::segment::SegmentHeader::decode(&bytes).ok()??;
571 (header.segment == number && header.base_lsn <= oldest.lsn.saturating_add(1))
572 .then_some(path.clone())
573 });
574 if let Some(boundary) = boundary {
575 for name in segments.into_iter().take_while(|n| *n < boundary) {
576 m.disk.remove(&name);
577 }
578 }
579 }
580 self.ckpt_done = Some(Ok(cp.lsn));
581 }
582
583 pub fn epoch(&self) -> u64 {
584 self.epoch
585 }
586}
587
588impl Env for SimEnv {
589 fn now_ms(&self) -> u64 {
590 self.m.borrow().now
591 }
592
593 fn random(&mut self) -> u64 {
594 self.rng.next()
595 }
596
597 fn set_notify(&mut self, _notify: Notify) {}
598
599 fn max_in_flight(&self) -> usize {
600 if self.log.is_some() {
601 self.log_max_in_flight
602 } else {
603 1
604 }
605 }
606
607 fn submit(&mut self, batch: Batch) {
608 if let Some(log) = &self.log {
609 self.log_in_flight = Some(batch.last_lsn);
610 let mut l = log.borrow_mut();
611 if l.wal.stage(self.shard, batch).is_err() {
612 l.failed = true;
613 }
614 l.start(&self.m);
615 return;
616 }
617 let mut m = self.m.borrow_mut();
618 for c in &batch.chunks {
619 m.disk
620 .file(&seg_name(self.shard, c.segment))
621 .write(c.offset, &c.bytes);
622 }
623 let after = m.latency();
624 let (shard, epoch) = (self.shard, self.epoch);
625 m.schedule(after, Io::Flush { shard, epoch });
626 self.flushing = Some(batch);
627 }
628
629 fn poll_flushed(&mut self) -> Option<io::Result<Lsn>> {
630 if let Some(log) = &self.log {
631 let want = self.log_in_flight?;
632 let l = log.borrow();
633 if l.failed {
634 self.log_in_flight = None;
635 return Some(Err(io::Error::other("simulated shared log failure")));
636 }
637 let durable = l.wal.durable(self.shard);
638 if durable >= want {
639 self.log_in_flight = None;
640 self.log_reported = durable;
641 return Some(Ok(durable));
642 }
643 if durable > self.log_reported {
644 self.log_reported = durable;
645 return Some(Ok(durable));
646 }
647 return None;
648 }
649 self.flushed.take()
650 }
651
652 fn store_append(&mut self, frame: &[u8]) -> io::Result<u64> {
653 let off = self.store_len;
654 let name = store_name(self.shard, self.store_gen);
655 self.m.borrow_mut().disk.file(&name).write(off, frame);
656 self.store_len += frame.len() as u64;
657 Ok(off)
658 }
659
660 fn store_read(&mut self, offset: u64, len: u32) -> io::Result<Vec<u8>> {
661 let name = store_name(self.shard, self.store_gen);
662 self.m
663 .borrow()
664 .disk
665 .get(&name)
666 .and_then(|f| f.read(offset, len as usize))
667 .ok_or_else(|| io::Error::other("run store read past end"))
668 }
669
670 fn store_len(&self) -> u64 {
671 self.store_len
672 }
673
674 fn store_generation(&self) -> u64 {
675 self.store_gen
676 }
677
678 fn compact_store(
679 &mut self,
680 live: &[(fusee_core::RunId, fusee_core::shard::BlobRef)],
681 ) -> io::Result<bool> {
682 let mut generation = self.store_gen;
683 let name = loop {
684 generation = generation
685 .checked_add(1)
686 .ok_or_else(|| io::Error::other("store generation overflow"))?;
687 let name = store_name(self.shard, generation);
688 if self.m.borrow().disk.get(&name).is_none() {
689 break name;
690 }
691 };
692 let header = store_header();
693 self.m.borrow_mut().disk.file(&name).write(0, &header);
694 let mut length = header.len() as u64;
695 for &(run, blob) in live {
696 let frame = self.store_read(blob.offset, blob.len)?;
697 fusee::env::check_live_frame(&frame, run)?;
698 self.m.borrow_mut().disk.file(&name).write(length, &frame);
699 length += u64::from(blob.len);
700 }
701 self.store_gen = generation;
702 self.store_len = length;
703 Ok(true)
704 }
705
706 fn checkpoint(&mut self, checkpoint: Checkpoint, delete_through_segment: Option<u64>) {
707 if self.log.is_some() {
708 self.log_checkpoints.push(checkpoint.lsn);
709 }
710 let mut m = self.m.borrow_mut();
711 let after = m.latency();
712 let (shard, epoch) = (self.shard, self.epoch);
713 m.schedule(after, Io::Checkpoint { shard, epoch });
714 self.ckpt = Some((checkpoint, delete_through_segment));
715 }
716
717 fn poll_checkpoint(&mut self) -> Option<io::Result<Lsn>> {
718 self.ckpt_done.take()
719 }
720
721 fn load_checkpoint(&mut self) -> io::Result<Option<Checkpoint>> {
722 let cp = self.load_newest_checkpoint()?;
723 self.indexed_prefix = cp
724 .as_ref()
725 .filter(|c| {
726 c.index.is_some() && !(self.epoch + u64::from(self.shard)).is_multiple_of(4)
727 })
728 .map(|c| (c.store_generation, c.store_len));
729 if self.log.is_some() {
730 let m = self.m.borrow();
731 let mut cps = m.disk.names_with_prefix(&format!("s{:03}/cp-", self.shard));
732 cps.sort();
733 let mut positions: Vec<u64> = cps
734 .iter()
735 .rev()
736 .filter_map(|n| m.disk.get(n))
737 .filter_map(|f| Checkpoint::decode(&f.view()).ok())
738 .map(|c| c.lsn)
739 .take(2)
740 .collect();
741 positions.reverse();
742 drop(m);
743 self.log_retained = if cp.is_some() { positions } else { vec![0] };
744 self.report_retained()?;
745 }
746 Ok(cp)
747 }
748
749 fn shared_log(&self) -> bool {
750 self.log.is_some()
751 }
752
753 fn recover_log(
754 &mut self,
755 after: Lsn,
756 ) -> io::Result<(Vec<(Lsn, fusee_core::record::RecordBuf)>, Lsn)> {
757 match &self.log {
758 Some(log) => log.borrow_mut().wal.take_recovered(self.shard, after),
759 None => Err(io::Error::other("this shard has per-shard segments")),
760 }
761 }
762
763 fn log_position(&self) -> u64 {
764 self.log.as_ref().map_or(0, |l| l.borrow().wal.position())
765 }
766
767 fn log_segment(&self) -> u64 {
768 self.log
769 .as_ref()
770 .map_or(0, |l| l.borrow().wal.active_segment())
771 }
772
773 fn checkpoint_wanted(&self) -> bool {
774 self.log
775 .as_ref()
776 .is_some_and(|l| l.borrow().wal.pins(self.shard))
777 }
778 fn checkpoint_wanted_on_close(&self) -> bool {
779 self.log
780 .as_ref()
781 .is_some_and(|l| l.borrow().wal.holds_old(self.shard))
782 }
783 fn recover_store(
784 &mut self,
785 generation: u64,
786 len: u64,
787 visit: &mut dyn FnMut(u64, u32, fusee_core::store::StoreEntry),
788 ) -> io::Result<()> {
789 let indexed = generation != 0 && self.indexed_prefix.take() == Some((generation, len));
790 let bytes = self.load_store(generation, len)?;
791 if indexed {
792 return Ok(());
793 }
794 fusee_core::store::scan_store(&bytes, visit).map_err(io::Error::other)
795 }
796
797 fn load_store(&mut self, generation: u64, len: u64) -> io::Result<Vec<u8>> {
798 let mut m = self.m.borrow_mut();
799 if generation == 0 {
800 let name = store_name(self.shard, 1);
801 m.disk.remove(&name);
802 let f = m.disk.file(&name);
803 let h = store_header();
804 f.write(0, &h);
805 f.flush();
806 self.store_gen = 1;
807 self.store_len = h.len() as u64;
808 return Ok(h);
809 }
810 let name = store_name(self.shard, generation);
811 let f = m.disk.file(&name);
812 f.truncate(len);
813 let bytes = f.view();
814 if (bytes.len() as u64) < len {
815 return Err(io::Error::other(
816 "run store shorter than its checkpoint says",
817 ));
818 }
819 self.store_gen = generation;
820 self.store_len = len;
821 Ok(bytes)
822 }
823
824 fn segments(&mut self) -> io::Result<Vec<u64>> {
825 let m = self.m.borrow();
826 let mut v: Vec<u64> = m
827 .disk
828 .names_with_prefix(&format!("s{:03}/log/", self.shard))
829 .iter()
830 .filter_map(|n| n.rsplit('/').next()?.parse().ok())
831 .collect();
832 v.sort_unstable();
833 Ok(v)
834 }
835
836 fn read_segment(&mut self, segment: u64) -> io::Result<Vec<u8>> {
837 let m = self.m.borrow();
838 m.disk
839 .get(&seg_name(self.shard, segment))
840 .map(|f| f.view())
841 .ok_or_else(|| io::Error::other("missing segment"))
842 }
843
844 fn sync_recovered(&mut self) -> io::Result<()> {
845 let mut m = self.m.borrow_mut();
846 for n in m
847 .disk
848 .names_with_prefix(&format!("s{:03}/log/", self.shard))
849 {
850 m.disk.file(&n).flush();
851 }
852 Ok(())
853 }
854
855 fn discard_segment(&mut self, segment: u64) -> io::Result<()> {
856 self.m
857 .borrow_mut()
858 .disk
859 .remove(&seg_name(self.shard, segment));
860 Ok(())
861 }
862
863 fn exclusive_log(&self) -> bool {
864 true
865 }
866
867 fn open_log(&mut self, segment: u64) -> io::Result<()> {
868 let mut m = self.m.borrow_mut();
869 let f = m.disk.file(&seg_name(self.shard, segment));
870 f.flush();
871 Ok(())
872 }
873}
874
875impl SimEnv {
876 fn load_newest_checkpoint(&mut self) -> io::Result<Option<Checkpoint>> {
877 let m = self.m.borrow();
878 let mut cps = m.disk.names_with_prefix(&format!("s{:03}/cp-", self.shard));
879 cps.sort();
880 for name in cps.iter().rev() {
881 if let Some(f) = m.disk.get(name)
882 && let Ok(cp) = Checkpoint::decode(&f.view())
883 {
884 return Ok(Some(cp));
885 }
886 }
887 Ok(None)
888 }
889}