Skip to main content

blockworx_store/storage/
erased.rs

1//! A storage whose kind is known only at run time.
2//!
3//! [`Storage`] answers futures of its own making, which is what keeps a
4//! ready one free of an allocation per read — and what stops it being a
5//! trait object. The editor's document handle holds one container without
6//! caring where its bytes are, so it holds an [`Any`]: the same trait, one
7//! box deep.
8
9use core::future::Future;
10use core::pin::Pin;
11use std::path::Path;
12
13use super::{Entry, Name, Residency, Storage};
14use crate::lock::Claim;
15use crate::record::WallTime;
16
17type Answer<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
18
19trait Erased {
20    fn name(&self) -> Name;
21    fn names(&self, at: &Entry) -> String;
22    fn residency(&self) -> Residency;
23    fn disk_path(&self) -> Option<&Path>;
24    fn read<'a>(&'a self, at: &'a Entry) -> Answer<'a, std::io::Result<Option<Vec<u8>>>>;
25    fn exists<'a>(&'a self, at: &'a Entry) -> Answer<'a, std::io::Result<bool>>;
26    fn write<'a>(&'a self, at: &'a Entry, bytes: &'a [u8]) -> Answer<'a, std::io::Result<()>>;
27    fn append<'a>(&'a self, at: &'a Entry, bytes: &'a [u8]) -> Answer<'a, std::io::Result<()>>;
28    fn truncate<'a>(&'a self, at: &'a Entry, len: u64) -> Answer<'a, std::io::Result<()>>;
29    fn list<'a>(&'a self, dir: &'a Entry) -> Answer<'a, std::io::Result<Vec<String>>>;
30    fn remove<'a>(&'a self, at: &'a Entry) -> Answer<'a, std::io::Result<()>>;
31    fn create_dir<'a>(&'a self, dir: &'a Entry) -> Answer<'a, std::io::Result<()>>;
32    fn rename<'a>(&'a mut self, to: &'a Name) -> Answer<'a, std::io::Result<()>>;
33    fn discard(&self) -> Answer<'_, std::io::Result<()>>;
34    fn claim(&self, now: WallTime) -> Answer<'_, std::io::Result<Claim>>;
35    fn release(&self);
36}
37
38impl<S: Storage> Erased for S {
39    fn name(&self) -> Name {
40        Storage::name(self)
41    }
42    fn names(&self, at: &Entry) -> String {
43        Storage::names(self, at)
44    }
45    fn residency(&self) -> Residency {
46        Storage::residency(self)
47    }
48    fn disk_path(&self) -> Option<&Path> {
49        Storage::disk_path(self)
50    }
51    fn read<'a>(&'a self, at: &'a Entry) -> Answer<'a, std::io::Result<Option<Vec<u8>>>> {
52        Box::pin(Storage::read(self, at))
53    }
54    fn exists<'a>(&'a self, at: &'a Entry) -> Answer<'a, std::io::Result<bool>> {
55        Box::pin(Storage::exists(self, at))
56    }
57    fn write<'a>(&'a self, at: &'a Entry, bytes: &'a [u8]) -> Answer<'a, std::io::Result<()>> {
58        Box::pin(Storage::write(self, at, bytes))
59    }
60    fn append<'a>(&'a self, at: &'a Entry, bytes: &'a [u8]) -> Answer<'a, std::io::Result<()>> {
61        Box::pin(Storage::append(self, at, bytes))
62    }
63    fn truncate<'a>(&'a self, at: &'a Entry, len: u64) -> Answer<'a, std::io::Result<()>> {
64        Box::pin(Storage::truncate(self, at, len))
65    }
66    fn list<'a>(&'a self, dir: &'a Entry) -> Answer<'a, std::io::Result<Vec<String>>> {
67        Box::pin(Storage::list(self, dir))
68    }
69    fn remove<'a>(&'a self, at: &'a Entry) -> Answer<'a, std::io::Result<()>> {
70        Box::pin(Storage::remove(self, at))
71    }
72    fn create_dir<'a>(&'a self, dir: &'a Entry) -> Answer<'a, std::io::Result<()>> {
73        Box::pin(Storage::create_dir(self, dir))
74    }
75    fn rename<'a>(&'a mut self, to: &'a Name) -> Answer<'a, std::io::Result<()>> {
76        Box::pin(Storage::rename(self, to))
77    }
78    fn discard(&self) -> Answer<'_, std::io::Result<()>> {
79        Box::pin(Storage::discard(self))
80    }
81    fn claim(&self, now: WallTime) -> Answer<'_, std::io::Result<Claim>> {
82        Box::pin(Storage::claim(self, now))
83    }
84    fn release(&self) {
85        Storage::release(self);
86    }
87}
88
89/// One container's bytes, wherever they are.
90pub struct Any(Box<dyn Erased>);
91
92impl Any {
93    pub fn new(storage: impl Storage + 'static) -> Self {
94        Self(Box::new(storage))
95    }
96}
97
98impl std::fmt::Debug for Any {
99    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100        write!(f, "storage for {}", self.0.name())
101    }
102}
103
104impl Storage for Any {
105    fn name(&self) -> Name {
106        self.0.name()
107    }
108    fn names(&self, at: &Entry) -> String {
109        self.0.names(at)
110    }
111    fn residency(&self) -> Residency {
112        self.0.residency()
113    }
114    fn disk_path(&self) -> Option<&Path> {
115        self.0.disk_path()
116    }
117    fn read<'a>(
118        &'a self,
119        at: &'a Entry,
120    ) -> impl Future<Output = std::io::Result<Option<Vec<u8>>>> + 'a {
121        self.0.read(at)
122    }
123    fn exists<'a>(&'a self, at: &'a Entry) -> impl Future<Output = std::io::Result<bool>> + 'a {
124        self.0.exists(at)
125    }
126    fn write<'a>(
127        &'a self,
128        at: &'a Entry,
129        bytes: &'a [u8],
130    ) -> impl Future<Output = std::io::Result<()>> + 'a {
131        self.0.write(at, bytes)
132    }
133    fn append<'a>(
134        &'a self,
135        at: &'a Entry,
136        bytes: &'a [u8],
137    ) -> impl Future<Output = std::io::Result<()>> + 'a {
138        self.0.append(at, bytes)
139    }
140    fn truncate<'a>(
141        &'a self,
142        at: &'a Entry,
143        len: u64,
144    ) -> impl Future<Output = std::io::Result<()>> + 'a {
145        self.0.truncate(at, len)
146    }
147    fn list<'a>(
148        &'a self,
149        dir: &'a Entry,
150    ) -> impl Future<Output = std::io::Result<Vec<String>>> + 'a {
151        self.0.list(dir)
152    }
153    fn remove<'a>(&'a self, at: &'a Entry) -> impl Future<Output = std::io::Result<()>> + 'a {
154        self.0.remove(at)
155    }
156    fn create_dir<'a>(&'a self, dir: &'a Entry) -> impl Future<Output = std::io::Result<()>> + 'a {
157        self.0.create_dir(dir)
158    }
159    fn rename<'a>(&'a mut self, to: &'a Name) -> impl Future<Output = std::io::Result<()>> + 'a {
160        self.0.rename(to)
161    }
162    fn discard(&self) -> impl Future<Output = std::io::Result<()>> + '_ {
163        self.0.discard()
164    }
165    fn claim(&self, now: WallTime) -> impl Future<Output = std::io::Result<Claim>> + '_ {
166        self.0.claim(now)
167    }
168    fn release(&self) {
169        self.0.release();
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176    use crate::container::LOCK;
177    use crate::fixture::block_on;
178    use crate::lock::Claim;
179    use crate::record::WallTime;
180    use crate::storage::{Memory, ready_now};
181
182    /// The box is the only difference: what a storage answers through it
183    /// is what it answers directly.
184    #[test]
185    fn a_boxed_storage_answers_as_the_one_inside_it() {
186        let held = Any::new(Memory::new("doc.bwx"));
187        let notes = Entry::fixed("notes.txt");
188        ready_now(Storage::write(&held, &notes, b"kept")).expect("the write lands");
189
190        assert_eq!(
191            ready_now(Storage::read(&held, &notes)).expect("it reads back"),
192            Some(b"kept".to_vec()),
193        );
194        assert_eq!(Storage::name(&held).to_string(), "doc.bwx");
195        assert_eq!(Storage::residency(&held), Residency::Lazy);
196        assert_eq!(Storage::disk_path(&held), None);
197    }
198
199    /// The lock travels through the box as the reads do: a storage whose
200    /// host keeps its lock is not made to write a lock entry by being
201    /// boxed, which is the one thing the editor's own handle would hide.
202    #[test]
203    fn a_boxed_storage_is_locked_the_way_the_one_inside_it_is() {
204        let held = Any::new(Memory::deferred("doc.bwx"));
205        assert!(matches!(
206            block_on(Storage::claim(&held, WallTime::EPOCH)).expect("the claim"),
207            Claim::Taken,
208        ));
209        assert_eq!(
210            block_on(Storage::read(&held, &LOCK)).expect("the read answers"),
211            None,
212            "the box wrote a lock entry the storage inside it has no use for",
213        );
214
215        assert!(matches!(
216            block_on(Storage::claim(&held, WallTime::EPOCH)).expect("the claim"),
217            Claim::Held(_),
218        ));
219        Storage::release(&held);
220        assert!(matches!(
221            block_on(Storage::claim(&held, WallTime::EPOCH)).expect("the claim"),
222            Claim::Taken,
223        ));
224    }
225}