use std::{
collections::VecDeque,
sync::{
atomic::{AtomicBool, Ordering},
Mutex, RwLock,
},
};
pub struct AccessCell<T> {
value: RwLock<T>,
running: AtomicBool,
queue: Mutex<VecDeque<Box<dyn FnOnce(&mut T) + Send>>>,
}
unsafe impl<T: Send> Send for AccessCell<T> {}
unsafe impl<T: Send> Sync for AccessCell<T> {}
impl<T> AccessCell<T> {
pub fn new(value: T) -> Self {
Self {
value: RwLock::new(value),
running: AtomicBool::new(false),
queue: Mutex::new(VecDeque::new()),
}
}
pub fn access(&self, f: impl FnOnce(&mut T) + Send + 'static) {
if self.running.swap(true, Ordering::Acquire) {
self.queue.lock().unwrap().push_back(Box::new(f));
return;
}
let mut guard = self.value.write().unwrap();
f(&mut *guard);
while let Some(job) = self.queue.lock().unwrap().pop_front() {
job(&mut *guard);
}
drop(guard);
self.running.store(false, Ordering::Release);
}
pub fn access_ref(&self) -> std::sync::RwLockReadGuard<'_, T> {
self.value.read().unwrap()
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use std::thread;
#[test]
fn test_access() {
let value = Arc::new(crate::AccessCell::new(0));
value.access({
let value = value.clone();
move |_| {
value.access(|v| {
*v = 10;
})
}
});
assert_eq!(*value.access_ref(), 10);
}
#[test]
fn test_access_multithreaded() {
let cell = Arc::new(crate::AccessCell::new(0i32));
let num_threads = 8;
let increments_per_thread = 100;
let handles: Vec<_> = (0..num_threads)
.map(|_| {
let cell = Arc::clone(&cell);
thread::spawn(move || {
for _ in 0..increments_per_thread {
cell.access(|v| *v += 1);
}
})
})
.collect();
for h in handles {
h.join().unwrap();
}
let expected = num_threads * increments_per_thread;
assert_eq!(*cell.access_ref(), expected);
}
}