Remote reads (range-read M2/M3: open_storage, HTTP/S3) and near-complete in-place editing #18
@@ -98,7 +98,7 @@ type Block = Arc<[u8]>;
|
|||||||
|
|
||||||
/// A fetch in progress: the readers waiting for a block wait on this.
|
/// A fetch in progress: the readers waiting for a block wait on this.
|
||||||
struct Flight {
|
struct Flight {
|
||||||
result: Mutex<Option<Result<Block, String>>>,
|
result: Mutex<Option<Result<Block, FormatError>>>,
|
||||||
done: Condvar,
|
done: Condvar,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ impl Flight {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(&self, r: Result<Block, String>) {
|
fn finish(&self, r: Result<Block, FormatError>) {
|
||||||
let mut slot = lock(&self.result);
|
let mut slot = lock(&self.result);
|
||||||
if slot.is_none() {
|
if slot.is_none() {
|
||||||
*slot = Some(r);
|
*slot = Some(r);
|
||||||
@@ -118,7 +118,7 @@ impl Flight {
|
|||||||
self.done.notify_all();
|
self.done.notify_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wait(&self) -> Result<Block, String> {
|
fn wait(&self) -> Result<Block, FormatError> {
|
||||||
let mut slot = lock(&self.result);
|
let mut slot = lock(&self.result);
|
||||||
loop {
|
loop {
|
||||||
if let Some(r) = slot.as_ref() {
|
if let Some(r) = slot.as_ref() {
|
||||||
@@ -173,10 +173,20 @@ pub struct BlockCache<S> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Fails every flight a fetch claimed and did not complete (an error or a
|
/// Fails every flight a fetch claimed and did not complete (an error or a
|
||||||
/// panic in the backend), so no reader waits forever.
|
/// panic in the backend), so no reader waits forever. The waiting readers
|
||||||
|
/// get the fetch's error.
|
||||||
struct FlightGuard<'a, S> {
|
struct FlightGuard<'a, S> {
|
||||||
cache: &'a BlockCache<S>,
|
cache: &'a BlockCache<S>,
|
||||||
flights: Vec<(u64, Arc<Flight>)>,
|
flights: Vec<(u64, Arc<Flight>)>,
|
||||||
|
error: Option<FormatError>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> FlightGuard<'_, S> {
|
||||||
|
/// Record `e` for the readers waiting on this fetch; returns it.
|
||||||
|
fn fail(&mut self, e: FormatError) -> FormatError {
|
||||||
|
self.error = Some(e.clone());
|
||||||
|
e
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> Drop for FlightGuard<'_, S> {
|
impl<S> Drop for FlightGuard<'_, S> {
|
||||||
@@ -191,8 +201,12 @@ impl<S> Drop for FlightGuard<'_, S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
drop(st);
|
drop(st);
|
||||||
|
let error = self
|
||||||
|
.error
|
||||||
|
.take()
|
||||||
|
.unwrap_or_else(|| FormatError::Storage("the fetch of this block failed".into()));
|
||||||
for (_, f) in &self.flights {
|
for (_, f) in &self.flights {
|
||||||
f.finish(Err("the fetch of this block failed".into()));
|
f.finish(Err(error.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -348,6 +362,7 @@ impl<S: Storage> BlockCache<S> {
|
|||||||
let mut guard = FlightGuard {
|
let mut guard = FlightGuard {
|
||||||
cache: self,
|
cache: self,
|
||||||
flights: Vec::new(),
|
flights: Vec::new(),
|
||||||
|
error: None,
|
||||||
};
|
};
|
||||||
{
|
{
|
||||||
let mut st = lock(&self.state);
|
let mut st = lock(&self.state);
|
||||||
@@ -410,13 +425,16 @@ impl<S: Storage> BlockCache<S> {
|
|||||||
self.counters
|
self.counters
|
||||||
.requests
|
.requests
|
||||||
.fetch_add(runs.len() as u64, Ordering::Relaxed);
|
.fetch_add(runs.len() as u64, Ordering::Relaxed);
|
||||||
let fetched = self.inner.read_ranges(&runs)?;
|
let fetched = match self.inner.read_ranges(&runs) {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(e) => return Err(guard.fail(e)),
|
||||||
|
};
|
||||||
if fetched.len() != runs.len() {
|
if fetched.len() != runs.len() {
|
||||||
return Err(FormatError::Storage(format!(
|
return Err(guard.fail(FormatError::Storage(format!(
|
||||||
"backend returned {} ranges for {} requested",
|
"backend returned {} ranges for {} requested",
|
||||||
fetched.len(),
|
fetched.len(),
|
||||||
runs.len()
|
runs.len()
|
||||||
)));
|
))));
|
||||||
}
|
}
|
||||||
let mut got: Vec<(u64, Block)> = Vec::with_capacity(guard.flights.len());
|
let mut got: Vec<(u64, Block)> = Vec::with_capacity(guard.flights.len());
|
||||||
for (run, bytes) in runs.iter().zip(&fetched) {
|
for (run, bytes) in runs.iter().zip(&fetched) {
|
||||||
@@ -424,12 +442,12 @@ impl<S: Storage> BlockCache<S> {
|
|||||||
.bytes_fetched
|
.bytes_fetched
|
||||||
.fetch_add(bytes.len() as u64, Ordering::Relaxed);
|
.fetch_add(bytes.len() as u64, Ordering::Relaxed);
|
||||||
if bytes.len() as u64 != run.end - run.start {
|
if bytes.len() as u64 != run.end - run.start {
|
||||||
return Err(FormatError::Storage(format!(
|
return Err(guard.fail(FormatError::Storage(format!(
|
||||||
"short read from the backend: {} of {} bytes at offset {}",
|
"short read from the backend: {} of {} bytes at offset {}",
|
||||||
bytes.len(),
|
bytes.len(),
|
||||||
run.end - run.start,
|
run.end - run.start,
|
||||||
run.start
|
run.start
|
||||||
)));
|
))));
|
||||||
}
|
}
|
||||||
let bs = self.config.block_size;
|
let bs = self.config.block_size;
|
||||||
let mut i = run.start / bs;
|
let mut i = run.start / bs;
|
||||||
@@ -476,12 +494,7 @@ impl<S: Storage> BlockCache<S> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (i, f) in waits {
|
for (i, f) in waits {
|
||||||
match f.wait() {
|
have.insert(i, f.wait()?);
|
||||||
Ok(data) => {
|
|
||||||
have.insert(i, data);
|
|
||||||
}
|
|
||||||
Err(e) => return Err(FormatError::Storage(e)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(have)
|
Ok(have)
|
||||||
}
|
}
|
||||||
@@ -786,6 +799,34 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A slow backend whose fetches all fail with a telling error.
|
||||||
|
struct SlowFailing;
|
||||||
|
|
||||||
|
impl Storage for SlowFailing {
|
||||||
|
fn read_at(&self, _: u64, _: usize) -> Result<Cow<'_, [u8]>, FormatError> {
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||||
|
Err(FormatError::Storage("the file changed while open".into()))
|
||||||
|
}
|
||||||
|
fn len(&self) -> u64 {
|
||||||
|
1 << 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn readers_waiting_on_a_failed_fetch_get_its_error() {
|
||||||
|
let c = BlockCache::new(SlowFailing, small());
|
||||||
|
std::thread::scope(|s| {
|
||||||
|
let hs: Vec<_> = (0..4)
|
||||||
|
.map(|_| s.spawn(|| c.read_at(0, 10).map(|b| b.len())))
|
||||||
|
.collect();
|
||||||
|
for h in hs {
|
||||||
|
let e = h.join().unwrap().unwrap_err().to_string();
|
||||||
|
assert!(e.contains("changed while open"), "{e}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assert!(c.stats().waits > 0);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn concurrent_readers_never_fetch_a_block_twice() {
|
fn concurrent_readers_never_fetch_a_block_twice() {
|
||||||
let d = data(64 * 1024);
|
let d = data(64 * 1024);
|
||||||
|
|||||||
@@ -301,11 +301,7 @@ fn a_server_that_ignores_range_is_refused_or_downloaded_when_allowed() {
|
|||||||
matches!(err, Error::Remote(RemoteError::RangeNotSupported(_))),
|
matches!(err, Error::Remote(RemoteError::RangeNotSupported(_))),
|
||||||
"{err}"
|
"{err}"
|
||||||
);
|
);
|
||||||
assert!(
|
assert_eq!(server.requests(), 1, "refused at the first response");
|
||||||
server.bytes() < bytes.len() as u64,
|
|
||||||
"refusing must not download the file ({} bytes read)",
|
|
||||||
server.bytes()
|
|
||||||
);
|
|
||||||
let mut opts = quick();
|
let mut opts = quick();
|
||||||
opts.http.allow_full_download = true;
|
opts.http.allow_full_download = true;
|
||||||
let storage = storage_for_url(&url, &opts).unwrap();
|
let storage = storage_for_url(&url, &opts).unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user