jonhoo / flurry

A port of Java's ConcurrentHashMap to Rust

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Replacing map elements incorrectly decrements the map's element count

domenicquirl opened this issue · comments

commented

I'm close to done with the initial work on #13. While looking at replaceNode I noticed a check where the Java code does this:
https://github.com/jonhoo/flurry/blob/master/jsr166/src/ConcurrentHashMap.java#L1141-L1148
That is (ignoring validated, which we don't have or need), it modifies the number of items in the map if there was an old value which got replaced and it got replaced by null, i.e. the node got deleted.
Our current code here only checks the first of these but not the second.

It is currently possible to reach the call to add_count even with a new_value of Some. These modified tests from map.rs show that while the old value does get replaced and an entry remains present in the map for the given key, the map's length gets incorrectly updated to 0:

#[test]
fn replace_existing() {
    let map = HashMap::<usize, usize>::new();
    {
        let guard = epoch::pin();
        map.insert(42, 42, &guard);
        assert_eq!(map.len(), 1); // Ok
        let old = map.replace_node(&42, Some(10), None, &guard);
        assert_eq!(old, Some((&42, &42)));
        assert_eq!(*map.get(&42, &guard).unwrap(), 10);
        assert_eq!(map.len(), 1); // Panics with left = 0
    }
}

#[test]
fn replace_existing_observed_value_matching() {
    let map = HashMap::<usize, usize>::new();
    {
        let guard = epoch::pin();
        map.insert(42, 42, &guard);
        assert_eq!(map.len(), 1); // Ok
        let observed_value = Shared::from(map.get(&42, &guard).unwrap() as *const _);
        let old = map.replace_node(&42, Some(10), Some(observed_value), &guard);
        assert_eq!(old, Some((&42, &42)));
        assert_eq!(*map.get(&42, &guard).unwrap(), 10);
        assert_eq!(map.len(), 1); // Panics with left = 0
    }
}

One reason this was not yet noticed could be that I think we don't currently expose any functionality that replaces with non-null values. In the Java code, this only happens through exposed replace methods.

commented

I could fix this together with the addition of TreeBins, since I'll have to move the check anyways. However I will be on holiday starting tomorrow and, while this is the last method to adapt to TreeBins, it will take some more time until everything is tested and ready to merge.

I'll file a PR shortly