I want to modify a HashMap from one thread and read it from another, where the reading thread is the UI thread so I do not want to block waiting for a Mutex on reads. So I've written the code below since I was unable to find well used and/or explained way to handle this use case.
type Map = HashMap<usize, u8>;
type Maps = [Mutex<Map>; 2];
pub fn new_handles(
capacity: usize,
) -> (ReadHandle, WriteHandle) {
let maps = Arc::new([
Mutex::new(Map::with_capacity(capacity)),
Mutex::new(Map::with_capacity(capacity)),
]);
(
ReadHandle {
maps: maps.clone(),
index: 0,
},
WriteHandle { maps },
)
}
/// There should be at most one `WriteHandle` for a given backing store of maps.
pub struct WriteHandle {
maps: Arc<Maps>,
}
impl WriteHandle {
fn perform_write<F>(&mut self, action: F)
where
F: Fn(&mut Map) -> (),
{
// Just in case we somehow would loop forever otherwise
let mut count = 0;
let mut wrote_to_0 = false;
let mut wrote_to_1 = false;
while count < 16 && !(wrote_to_0 && wrote_to_1) {
if !wrote_to_0 {
if let Ok(ref mut map) = self.maps.maps[0].try_lock() {
action(map);
wrote_to_0 = true;
}
}
if !wrote_to_1 {
if let Ok(ref mut map) = self.maps.maps[1].try_lock() {
action(map);
wrote_to_1 = true;
}
}
count += 1;
}
debug_assert!(wrote_to_1 && wrote_to_2);
}
}
pub struct ReadHandle {
maps: Arc<Maps>,
index: u8,
}
impl ReadHandle {
fn get(&mut self) -> Map {
if let Ok(read_ref) = self.maps[self.index as usize].try_lock() {
return (*read_ref).clone();
}
self.index += 1;
self.index &= 1;
if let Ok(read_ref) = self.maps[self.index as usize].try_lock() {
return read_ref.clone();
}
debug_assert!(false, "neither of the maps were free to read!");
Map::with_capacity(0)
}
}
I wrote some tests to try and make sure that the writes all went through and that debug_assert
s did not trigger under expected use, which involved making multiple threads and sending messages over channels. But the tests would fail and succeed apparently randomly. For example, if I compile a test binary containing multiple tests, and don't filter any out, one of the tests fails. But if I filter to just that test, it passes! This is even running with --test-threads=1
, so I don't know how to narrow down what is causing the issue.