use fs_core::block::{BlockDevice, BlockRead};
use fs_core::error::{Error, Result};
use fs_core::CachingDevice;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{Receiver, Sender};
use std::sync::{Arc, Mutex};
use std::thread;
mod common;
struct ParkingDevice {
data: Mutex<Vec<u8>>,
park_in_read: AtomicBool,
park_in_write: AtomicBool,
sampled: Mutex<Sender<()>>,
release: Mutex<Receiver<()>>,
}
impl ParkingDevice {
fn park(&self) {
self.sampled.lock().unwrap().send(()).unwrap();
self.release.lock().unwrap().recv().unwrap();
}
}
impl BlockRead for ParkingDevice {
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
{
let d = self.data.lock().unwrap();
common::read_into(&d, offset, buf)?;
}
if self.park_in_read.swap(false, Ordering::SeqCst) {
self.park();
}
Ok(())
}
fn size_bytes(&self) -> u64 {
self.data.lock().unwrap().len() as u64
}
}
impl BlockDevice for ParkingDevice {
fn write_at(&self, offset: u64, buf: &[u8]) -> Result<()> {
if self.park_in_write.swap(false, Ordering::SeqCst) {
self.park();
}
let mut d = self.data.lock().unwrap();
let size = d.len() as u64;
common::write_from(&mut d, offset, buf).map_err(|_| Error::OutOfBounds {
offset,
len: buf.len() as u64,
size,
})
}
fn is_writable(&self) -> bool {
true
}
}
#[test]
fn a_read_racing_a_write_does_not_leave_pre_write_bytes_in_the_cache() {
let (sampled_tx, sampled_rx) = std::sync::mpsc::channel();
let (release_tx, release_rx) = std::sync::mpsc::channel();
let dev = Arc::new(ParkingDevice {
data: Mutex::new(vec![0xAA; 8]),
park_in_read: AtomicBool::new(true),
park_in_write: AtomicBool::new(false),
sampled: Mutex::new(sampled_tx),
release: Mutex::new(release_rx),
});
let cache = CachingDevice::new(dev.clone(), 8, 4);
let reader = {
let cache = cache.clone();
thread::spawn(move || {
let mut buf = [0u8; 8];
cache.read_at(0, &mut buf).expect("racing read");
buf
})
};
sampled_rx.recv().expect("the reader sampled the device");
cache.write_at(0, &[0xBB; 8]).expect("write");
release_tx.send(()).expect("release the reader");
let raced = reader.join().expect("reader thread");
assert_eq!(
raced, [0xAA; 8],
"the racing read legitimately saw pre-write bytes — it began before the write"
);
let mut direct = [0u8; 8];
dev.read_at(0, &mut direct)
.expect("straight off the device");
assert_eq!(direct, [0xBB; 8], "the device really did take the write");
let mut after = [0u8; 8];
cache.read_at(0, &mut after).expect("read after the write");
assert_eq!(
after, [0xBB; 8],
"the cache is serving pre-write bytes after a completed write"
);
}
#[test]
fn a_read_that_completes_inside_a_write_does_not_survive_it() {
let (sampled_tx, sampled_rx) = std::sync::mpsc::channel();
let (release_tx, release_rx) = std::sync::mpsc::channel();
let dev = Arc::new(ParkingDevice {
data: Mutex::new(vec![0xAA; 8]),
park_in_read: AtomicBool::new(false),
park_in_write: AtomicBool::new(true),
sampled: Mutex::new(sampled_tx),
release: Mutex::new(release_rx),
});
let cache = CachingDevice::new(dev.clone(), 8, 4);
let writer = {
let cache = cache.clone();
thread::spawn(move || cache.write_at(0, &[0xBB; 8]).expect("write"))
};
sampled_rx.recv().expect("the write reached the device");
let mut during = [0u8; 8];
cache
.read_at(0, &mut during)
.expect("read during the write");
assert_eq!(
during, [0xAA; 8],
"the read began before the write landed, so these bytes are legitimate"
);
release_tx.send(()).expect("release the writer");
writer.join().expect("writer thread");
let mut direct = [0u8; 8];
dev.read_at(0, &mut direct)
.expect("straight off the device");
assert_eq!(direct, [0xBB; 8], "the device really did take the write");
let mut after = [0u8; 8];
cache.read_at(0, &mut after).expect("read after the write");
assert_eq!(
after, [0xBB; 8],
"the cache is serving bytes it fetched before the write landed"
);
}