imago/storage/drivers.rs
1//! Internal functionality for storage drivers.
2
3use crate::misc_helpers::Overlaps;
4#[cfg(feature = "async")]
5use futures::stream::{FuturesUnordered, StreamExt};
6use maybe_async::maybe_async;
7use std::ops::Range;
8use std::sync::atomic::{AtomicUsize, Ordering};
9#[cfg(feature = "sync")]
10use std::sync::mpsc::{self, Sender};
11use std::sync::Arc;
12#[cfg(feature = "async")]
13use tokio::sync::oneshot::{self, Sender};
14
15/// Helper object for the [`StorageExt`](crate::StorageExt) implementation.
16///
17/// State such as write blockers needs to be kept somewhere, and instead of introducing a wrapper
18/// (that might be bypassed), we store it directly in the [`Storage`](crate::Storage) objects so it
19/// cannot be bypassed (at least when using the [`StorageExt`](crate::StorageExt) methods).
20///
21/// Async note: Overlapping write blockers from different async tasks are safe (the awaiting task
22/// yields, letting the holding task make progress). Same-task overlap still deadlocks, as the
23/// task cannot drop its guard while suspended.
24///
25/// Sync note: Unlike in async mode, write blocker acquisition must not be nested on the same
26/// thread. Acquiring a blocker while already holding an overlapping one will deadlock on
27/// `recv()`, because the existing blocker can only be released by the same (now blocked) thread.
28#[derive(Debug, Default)]
29pub struct CommonStorageHelper {
30 /// Current in-flight write that allow concurrent writes to the same region.
31 ///
32 /// Normal non-async RwLock, so do not await while locked!
33 weak_write_blockers: std::sync::RwLock<RangeBlockedList>,
34
35 /// Current in-flight write that do not allow concurrent writes to the same region.
36 strong_write_blockers: std::sync::RwLock<RangeBlockedList>,
37}
38
39/// A list of ranges blocked for some kind of concurrent access.
40///
41/// Depending on the use, some will block all concurrent access (i.e. serializing writes will block
42/// both serializing and non-serializing writes (strong blockers)), while others will only block a
43/// subset (non-serializing writes will only block serializing writes (weak blockers)).
44#[derive(Debug, Default)]
45struct RangeBlockedList {
46 /// The list of ranges.
47 ///
48 /// Serializing writes (strong write blockers) are supposed to be rare, so it is important that
49 /// entering and removing items into/from this list is cheap, not that iterating it is.
50 blocked: Vec<Arc<RangeBlocked>>,
51}
52
53/// A range blocked for some kind of concurrent access.
54#[derive(Debug)]
55struct RangeBlocked {
56 /// The range.
57 range: Range<u64>,
58
59 /// List of requests awaiting the range to become unblocked.
60 ///
61 /// When the corresponding `RangeBlockedGuard` is dropped, these will all be awoken (via
62 /// `oneshot::Sender::send(())`).
63 ///
64 /// Normal non-async mutex, so do not await while locked!
65 waitlist: std::sync::Mutex<Vec<Sender<()>>>,
66
67 /// Index in the corresponding `RangeBlockedList.blocked` list, so it can be dropped quickly.
68 ///
69 /// (When the corresponding `RangeBlockedGuard` is dropped, this entry is swap-removed from the
70 /// `blocked` list, and the other entry taking its place has its `index` updated.)
71 ///
72 /// Only access under `blocked` lock!
73 index: AtomicUsize,
74
75 /// For debugging only: Thread that created this blocker, for reentrancy detection.
76 #[cfg(all(feature = "sync", debug_assertions))]
77 owner_thread: std::thread::ThreadId,
78}
79
80/// Keeps a `RangeBlocked` alive.
81///
82/// When dropped, removes the `RangeBlocked` from its list, and wakes all requests in the `waitlist`.
83#[derive(Debug)]
84pub struct RangeBlockedGuard<'a> {
85 /// List where this blocker resides.
86 list: &'a std::sync::RwLock<RangeBlockedList>,
87
88 /// `Option`, so `drop()` can `take()` it and unwrap the `Arc`.
89 ///
90 /// Consequently, do not clone: Must have refcount 1 when dropped. (The only clone must be in
91 /// `self.list.blocked`, under index `self.block.index`.)
92 block: Option<Arc<RangeBlocked>>,
93}
94
95#[maybe_async]
96impl CommonStorageHelper {
97 /// Await concurrent strong write blockers for the given range.
98 ///
99 /// Strong write blockers are set up for writes that must not be intersected by any other
100 /// write. Await such intersecting concurrent write requests, and return a guard that will
101 /// delay such new writes until the guard is dropped.
102 pub async fn weak_write_blocker(&self, range: Range<u64>) -> RangeBlockedGuard<'_> {
103 #[cfg(all(feature = "sync", debug_assertions))]
104 Self::assert_no_same_thread_overlap(&self.strong_write_blockers, &range);
105
106 #[cfg(feature = "async")]
107 let mut intersecting = FuturesUnordered::new();
108 #[cfg(feature = "sync")]
109 let mut intersecting = Vec::new();
110
111 // Create `RangeBlockedGuard` before the `await` below, so if the future is dropped,
112 // `RangeBlockedGuard::drop()` will run, removing the blocker from the list
113 let guard = {
114 // Consistent ordering to avoid deadlock: Always acquire weak before strong
115 let mut weak = self.weak_write_blockers.write().unwrap();
116 let strong = self.strong_write_blockers.read().unwrap();
117
118 strong.collect_intersecting(&range, &mut intersecting);
119
120 RangeBlockedGuard {
121 list: &self.weak_write_blockers,
122 block: Some(weak.block(range)),
123 }
124 };
125
126 // `RecvError` means the blocker's guard was dropped without signaling, so the blocking
127 // operation is gone, and thus waiting for it is pointless. We must still wait for all
128 // other overlapping blockers, so drain until all are actually done, ignoring errors.
129 #[cfg(feature = "async")]
130 while intersecting.next().await.is_some() {}
131 #[cfg(feature = "sync")]
132 for rx in intersecting {
133 let _ = rx.recv();
134 }
135
136 guard
137 }
138
139 /// Await any concurrent write request for the given range.
140 ///
141 /// Block the given range for any concurrent write requests until the returned guard object is
142 /// dropped. Existing requests are awaited, and new ones will be delayed.
143 pub async fn strong_write_blocker(&self, range: Range<u64>) -> RangeBlockedGuard<'_> {
144 #[cfg(all(feature = "sync", debug_assertions))]
145 {
146 Self::assert_no_same_thread_overlap(&self.weak_write_blockers, &range);
147 Self::assert_no_same_thread_overlap(&self.strong_write_blockers, &range);
148 }
149
150 #[cfg(feature = "async")]
151 let mut intersecting = FuturesUnordered::new();
152 #[cfg(feature = "sync")]
153 let mut intersecting = Vec::new();
154
155 // Create `RangeBlockedGuard` before the `await` below, so if the future is dropped,
156 // `RangeBlockedGuard::drop()` will run, removing the blocker from the list
157 let guard = {
158 // Consistent ordering to avoid deadlock: Always acquire weak before strong
159 let weak = self.weak_write_blockers.read().unwrap();
160 let mut strong = self.strong_write_blockers.write().unwrap();
161
162 weak.collect_intersecting(&range, &mut intersecting);
163 strong.collect_intersecting(&range, &mut intersecting);
164
165 RangeBlockedGuard {
166 list: &self.strong_write_blockers,
167 block: Some(strong.block(range)),
168 }
169 };
170
171 // `RecvError` means the blocker's guard was dropped without signaling, so the blocking
172 // operation is gone, and thus waiting for it is pointless. We must still wait for all
173 // other overlapping blockers, so drain until all are actually done, ignoring errors.
174 #[cfg(feature = "async")]
175 while intersecting.next().await.is_some() {}
176 #[cfg(feature = "sync")]
177 for rx in intersecting {
178 let _ = rx.recv();
179 }
180
181 guard
182 }
183
184 /// Panic if the current thread already holds a blocker in `list` that overlaps `range`.
185 ///
186 /// In sync mode, blocking on `recv()` to wait for an overlapping blocker held by the same
187 /// thread would deadlock, because that blocker can only be released by this (now blocked)
188 /// thread. This check runs before any locks are acquired so that a panic does not poison
189 /// them, allowing already-held guards to drop cleanly during unwinding.
190 #[cfg(all(feature = "sync", debug_assertions))]
191 fn assert_no_same_thread_overlap(
192 list: &std::sync::RwLock<RangeBlockedList>,
193 range: &Range<u64>,
194 ) {
195 let list = list.read().unwrap();
196 let current = std::thread::current().id();
197 for rb in &list.blocked {
198 if rb.range.overlaps(range) && rb.owner_thread == current {
199 panic!(
200 "Same-thread reentrancy: already holding write blocker for {:?}, \
201 acquiring overlapping blocker for {range:?} would deadlock",
202 rb.range,
203 );
204 }
205 }
206 }
207}
208
209impl RangeBlockedList {
210 /// Collects futures/receivers to await intersecting request.
211 ///
212 /// Creates a channel for every intersecting request; blocking on the receiver will wait for
213 /// the request to complete.
214 fn collect_intersecting(
215 &self,
216 check_range: &Range<u64>,
217 #[cfg(feature = "async")] intersecting: &mut FuturesUnordered<oneshot::Receiver<()>>,
218 #[cfg(feature = "sync")] intersecting: &mut Vec<mpsc::Receiver<()>>,
219 ) {
220 for range_block in self.blocked.iter() {
221 if range_block.range.overlaps(check_range) {
222 #[cfg(feature = "async")]
223 let (s, r) = oneshot::channel::<()>();
224 #[cfg(feature = "sync")]
225 let (s, r) = mpsc::channel();
226
227 range_block.waitlist.lock().unwrap().push(s);
228 intersecting.push(r);
229 }
230 }
231 }
232
233 /// Enter a new blocked range into the list.
234 ///
235 /// This only blocks new requests, old requests must separately be waited for by blocking on
236 /// all receivers returned by `collect_intersecting()`.
237 fn block(&mut self, range: Range<u64>) -> Arc<RangeBlocked> {
238 let range_block = Arc::new(RangeBlocked {
239 range,
240 waitlist: Default::default(),
241 index: self.blocked.len().into(),
242 #[cfg(all(feature = "sync", debug_assertions))]
243 owner_thread: std::thread::current().id(),
244 });
245 self.blocked.push(Arc::clone(&range_block));
246 range_block
247 }
248}
249
250impl Drop for RangeBlockedGuard<'_> {
251 fn drop(&mut self) {
252 let block = self.block.take().unwrap();
253
254 {
255 let mut list = self.list.write().unwrap();
256 let i = block.index.load(Ordering::Relaxed);
257 let removed = list.blocked.swap_remove(i);
258 debug_assert!(Arc::ptr_eq(&removed, &block));
259 if let Some(block) = list.blocked.get(i) {
260 block.index.store(i, Ordering::Relaxed);
261 }
262 }
263
264 let block = Arc::into_inner(block).unwrap();
265 let waitlist = block.waitlist.into_inner().unwrap();
266 for waiting in waitlist {
267 // If the receiving end was dropped (e.g. because the request was dropped), then just
268 // ignore that
269 let _ = waiting.send(());
270 }
271 }
272}
273
274#[cfg(all(feature = "sync", test, debug_assertions))]
275mod tests {
276 use super::*;
277
278 #[test]
279 #[should_panic(expected = "Same-thread reentrancy: already holding write blocker")]
280 fn test_weak_then_overlapping_strong() {
281 let helper = CommonStorageHelper::default();
282 let _weak = helper.weak_write_blocker(0..100);
283 let _strong = helper.strong_write_blocker(50..150);
284 }
285
286 #[test]
287 #[should_panic(expected = "Same-thread reentrancy: already holding write blocker")]
288 fn test_strong_then_overlapping_strong() {
289 let helper = CommonStorageHelper::default();
290 let _first = helper.strong_write_blocker(0..100);
291 let _second = helper.strong_write_blocker(50..150);
292 }
293
294 #[test]
295 #[should_panic(expected = "Same-thread reentrancy: already holding write blocker")]
296 fn test_strong_then_overlapping_weak() {
297 let helper = CommonStorageHelper::default();
298 let _strong = helper.strong_write_blocker(0..100);
299 let _weak = helper.weak_write_blocker(50..150);
300 }
301
302 #[test]
303 fn test_non_overlapping() {
304 let helper = CommonStorageHelper::default();
305 let _first = helper.weak_write_blocker(0..100);
306 let _second = helper.strong_write_blocker(100..200);
307 }
308
309 #[test]
310 fn test_weak_then_overlapping_weak() {
311 let helper = CommonStorageHelper::default();
312 let _first = helper.weak_write_blocker(0..100);
313 let _second = helper.weak_write_blocker(50..150);
314 }
315}