made BlobManager and Blobs thread-safe

This commit is contained in:
2026-05-29 16:45:13 -04:00
parent daeadf81ce
commit 7eceedd928
+95 -19
View File
@@ -1,5 +1,7 @@
#![allow(unused)] #![allow(unused)]
use std::sync::{Arc, Mutex, RwLock};
#[derive(PartialEq, Debug, Clone, Copy)] #[derive(PartialEq, Debug, Clone, Copy)]
struct Range { struct Range {
// todo: consider just using a map instead // todo: consider just using a map instead
@@ -155,43 +157,58 @@ struct BlobManager {
// fine if we assume: // fine if we assume:
// * IDs are sequentially generated // * IDs are sequentially generated
// * and IDs can be re-used // * and IDs can be re-used
blobs: Vec<Option<Blob>>, blobs: RwLock<Vec<Option<Arc<Mutex<Blob>>>>>,
// Also note RwLock (create) starvation might be a concern if caller is
// append/read heavy due to platform-defined fairness
// todo: better approach?
} }
// todo: blob indexing is left up to the caller maybe add ID generator
// for cross-thread synchronization or change approach
impl BlobManager { impl BlobManager {
pub fn new() -> Self { pub fn new() -> Self {
Self { blobs: Vec::new() } Self {
blobs: RwLock::new(Vec::new()),
}
} }
// todo: is usize appropriate datatype for ID? // todo: is usize appropriate datatype for ID?
pub fn create_blob(&mut self, id: usize) -> Result<(), BlobError> { pub fn create_blob(&self, id: usize) -> Result<(), BlobError> {
if let Some(Some(_)) = self.blobs.get_mut(id) { let mut blobs = self.blobs.write().unwrap();
if let Some(Some(_)) = blobs.get(id) {
return Err(BlobError::BlobExists); return Err(BlobError::BlobExists);
} else if id >= self.blobs.len() { } else if id >= blobs.len() {
// todo: better strategy possible? // todo: better strategy possible?
self.blobs.resize_with(id + 1, || None); blobs.resize_with(id + 1, || None);
} }
self.blobs[id] = Some(Blob::new()); blobs[id] = Some(Arc::new(Mutex::new(Blob::new())));
Ok(()) Ok(())
} }
pub fn append(&mut self, id: usize, input: &[u8]) -> Result<(), BlobError> { pub fn append(&self, id: usize, input: &[u8]) -> Result<(), BlobError> {
if let Some(Some(b)) = self.blobs.get_mut(id) { let blob = {
b.append(input); let blobs = self.blobs.read().unwrap();
return Ok(()); match blobs.get(id) {
Some(Some(b)) => Arc::clone(b),
_ => return Err(BlobError::BlobDNE),
} }
Err(BlobError::BlobDNE) };
blob.lock().unwrap().append(input);
Ok(())
} }
pub fn read(&mut self, id: usize, start: usize, len: usize) -> Result<Vec<u8>, BlobError> { pub fn read(&self, id: usize, start: usize, len: usize) -> Result<Vec<u8>, BlobError> {
if let Some(Some(b)) = self.blobs.get_mut(id) { let blob = {
let blobs = self.blobs.read().unwrap();
match blobs.get(id) {
Some(Some(b)) => Arc::clone(b),
_ => return Err(BlobError::BlobDNE),
}
};
let mut read = vec![0; len]; let mut read = vec![0; len];
b.read(start, len, &mut read)?; // propagate error to caller blob.lock().unwrap().read(start, len, &mut read)?; // propagate error to caller
// todo: if read clears a blob entirely should the blob be removed? // todo: if read clears a blob entirely should the blob be removed?
Ok(read) Ok(read)
} else {
Err(BlobError::BlobDNE)
}
} }
} }
@@ -257,9 +274,68 @@ mod tests {
assert_eq!(b.valid_ranges, &[Range::new(3, 4), Range::new(5, 6)]); assert_eq!(b.valid_ranges, &[Range::new(3, 4), Range::new(5, 6)]);
} }
#[test]
fn test_blob_manager_threaded_read() {
use std::thread;
let bm = Arc::new(BlobManager::new());
let handles: Vec<_> = (0..2usize)
.map(|i| {
let bm = Arc::clone(&bm);
thread::spawn(move || {
let len = 10_000_000; // 100 MB test takes a while
let data = vec![i as u8; len];
bm.create_blob(i).unwrap();
bm.append(i, &data).unwrap();
for j in 0..len as usize {
let byte = bm.read(i, j, 1).unwrap();
assert_eq!(byte[0], i as u8);
}
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
}
#[test]
fn test_blob_manager_threaded_create() {
use std::thread;
let bm = Arc::new(BlobManager::new());
let even = {
let bm = Arc::clone(&bm);
thread::spawn(move || {
for i in (0..20000usize).step_by(2) {
bm.create_blob(i).unwrap();
}
})
};
let odd = {
let bm = Arc::clone(&bm);
thread::spawn(move || {
for i in (1..20000usize).step_by(2) {
bm.create_blob(i).unwrap();
}
})
};
even.join().unwrap();
odd.join().unwrap();
let blobs = bm.blobs.read().unwrap();
assert_eq!(blobs.len(), 20000);
assert!(blobs.iter().all(|b| b.is_some()));
}
#[test] #[test]
fn test_blob_manager() { fn test_blob_manager() {
let mut bm = BlobManager::new(); let bm = BlobManager::new();
bm.create_blob(0); bm.create_blob(0);
bm.append(0, &[b'a', b'b', b'c']); bm.append(0, &[b'a', b'b', b'c']);
let v = Vec::from([b'a', b'b']); let v = Vec::from([b'a', b'b']);