libblobd_direct/backing_store/
mod.rs

1pub mod file;
2#[cfg(target_os = "linux")]
3pub mod uring;
4
5use async_trait::async_trait;
6use bufpool::buf::Buf;
7use off64::u64;
8use std::sync::Arc;
9
10/*
11
12Why use three different types instead of simply one?
13- Offsets: lots of subtleties due to different levels of offsets, with different interpretations by different components. For example, the journal always writes in offsets relative to the partition, but if there's only one type, then a bounded type only knows about itself and the root (i.e. device), giving incorrect offsets.
14- Names: similar to the previous point, the type name gets quite confusing, as some are used partition level while others are bounded to a specific range, but they all have the same type and API.
15
16Using a generic trait for BackingStore allows us to provide different variants for testing, in-memory, different platforms, user configurability, etc.
17
18*/
19
20#[async_trait]
21pub(crate) trait BackingStore: Send + Sync {
22  /// `offset` and `len` must be multiples of the underlying device's sector size.
23  async fn read_at(&self, offset: u64, len: u64) -> Buf;
24
25  /// `offset` and `data.len()` must be multiples of the underlying device's sector size.
26  /// Returns the original `data` so that it can be reused, if desired.
27  async fn write_at(&self, offset: u64, data: Buf) -> Buf;
28
29  /// Even when using direct I/O, `fsync` is still necessary, as it ensures the device itself has flushed any internal caches.
30  async fn sync(&self);
31}
32
33#[derive(Clone)]
34pub(crate) struct PartitionStore {
35  backing_store: Arc<dyn BackingStore>,
36  offset: u64,
37  len: u64,
38}
39
40impl PartitionStore {
41  pub fn new(backing_store: Arc<dyn BackingStore>, offset: u64, len: u64) -> Self {
42    Self {
43      backing_store,
44      offset,
45      len,
46    }
47  }
48
49  pub fn offset(&self) -> u64 {
50    self.offset
51  }
52
53  pub fn len(&self) -> u64 {
54    self.len
55  }
56
57  pub async fn read_at(&self, offset: u64, len: u64) -> Buf {
58    assert!(
59      offset + len <= self.len,
60      "attempted to read at {} with length {} but partition has length {}",
61      offset,
62      len,
63      self.len
64    );
65    self.backing_store.read_at(self.offset + offset, len).await
66  }
67
68  pub async fn write_at(&self, offset: u64, data: Buf) {
69    let len = u64!(data.len());
70    assert!(
71      offset + len <= self.len,
72      "attempted to write at {} with length {} but partition has length {}",
73      offset,
74      len,
75      self.len
76    );
77    self
78      .backing_store
79      .write_at(self.offset + offset, data)
80      .await;
81  }
82
83  pub async fn sync(&self) {
84    self.backing_store.sync().await;
85  }
86
87  /// `offset` must be a multiple of the underlying device's sector size.
88  pub fn bounded(&self, offset: u64, len: u64) -> BoundedStore {
89    assert!(offset + len <= self.len);
90    BoundedStore {
91      partition_store: self.clone(),
92      offset,
93      len,
94    }
95  }
96}
97
98#[derive(Clone)]
99pub(crate) struct BoundedStore {
100  partition_store: PartitionStore,
101  offset: u64,
102  len: u64,
103}
104
105#[allow(unused)]
106impl BoundedStore {
107  pub fn len(&self) -> u64 {
108    self.len
109  }
110
111  pub async fn read_at(&self, offset: u64, len: u64) -> Buf {
112    assert!(offset + len <= self.len);
113    self
114      .partition_store
115      .read_at(self.offset + offset, len)
116      .await
117  }
118
119  pub async fn write_at(&self, offset: u64, data: Buf) {
120    assert!(offset + u64!(data.len()) <= self.len);
121    self
122      .partition_store
123      .write_at(self.offset + offset, data)
124      .await;
125  }
126}