fixed inconsistent hash calculation

This commit is contained in:
Alexey Kudravtsev
2013-08-21 13:58:01 +04:00
parent f7725fbf1f
commit b8233bcf46
5 changed files with 175 additions and 61 deletions
@@ -22,6 +22,7 @@
*/
package com.intellij.util.containers;
import com.intellij.util.IncorrectOperationException;
import gnu.trove.TObjectHashingStrategy;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.TestOnly;
@@ -33,7 +34,12 @@ import java.util.concurrent.ConcurrentMap;
/**
* Fully copied from java.util.WeakHashMap except "get" method optimization.
*/
abstract class ConcurrentRefHashMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V> {
abstract class ConcurrentRefHashMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V>, TObjectHashingStrategy<K> {
protected final ReferenceQueue<K> myReferenceQueue = new ReferenceQueue<K>();
private final ConcurrentHashMap<Key<K, V>, V> myMap; // hashing strategy must be canonical, we compute corresponding hash codes using our own myHashingStrategy
@NotNull
private final TObjectHashingStrategy<K> myHashingStrategy;
public interface Key<K, V> {
K get();
@@ -45,22 +51,16 @@ abstract class ConcurrentRefHashMap<K, V> extends AbstractMap<K, V> implements C
int hashCode();
}
protected abstract Key<K, V> createKey(@NotNull K key, V value);
protected abstract Key<K, V> createKey(@NotNull K key, V value, @NotNull TObjectHashingStrategy<K> hashingStrategy);
private static class HardKey<K, V> implements Key<K, V> {
private K myKey;
private int myHash;
private final V value;
public HardKey(K key, V value) {
this.value = value;
setKey(key);
}
private void setKey(K key) {
private void setKey(K key, final int hash) {
myKey = key;
myHash = key == null ? 0 : key.hashCode();
myHash = hash;
}
@Override
@@ -70,7 +70,7 @@ abstract class ConcurrentRefHashMap<K, V> extends AbstractMap<K, V> implements C
@Override
public V getValue() {
return value;
return null;
}
public boolean equals(Object o) {
@@ -88,7 +88,6 @@ abstract class ConcurrentRefHashMap<K, V> extends AbstractMap<K, V> implements C
}
}
private final ConcurrentHashMap<Key<K, V>, V> myMap;
private static final Key NULL_KEY = new Key() {
@Override
public Object get() {
@@ -101,46 +100,61 @@ abstract class ConcurrentRefHashMap<K, V> extends AbstractMap<K, V> implements C
}
};
protected final ReferenceQueue<K> myReferenceQueue = new ReferenceQueue<K>();
// returns true if some keys were processed
boolean processQueue() {
Key<K, V> wk;
boolean processed = false;
while ((wk = (Key)myReferenceQueue.poll()) != null) {
V value = wk.getValue();
myMap.remove(wk, wk.hashCode(), value);
boolean remove = myMap.remove(wk, wk.hashCode(), value);
//System.out.println("key tossed: "+wk+" "+remove);
if (!remove) {
int i =0;
boolean remove2 = myMap.remove(wk, wk.hashCode(), value);
}
processed = true;
}
return processed;
}
public ConcurrentRefHashMap(int initialCapacity, float loadFactor) {
myMap = new ConcurrentHashMap<Key<K, V>, V>(initialCapacity, loadFactor, 4);
}
public ConcurrentRefHashMap(int initialCapacity) {
myMap = new ConcurrentHashMap<Key<K, V>, V>(initialCapacity);
public ConcurrentRefHashMap(Map<? extends K, ? extends V> t) {
this(Math.max(2 * t.size(), 11), ConcurrentHashMap.DEFAULT_LOAD_FACTOR);
putAll(t);
}
public ConcurrentRefHashMap() {
myMap = new ConcurrentHashMap<Key<K, V>, V>();
this(ConcurrentHashMap.DEFAULT_INITIAL_CAPACITY);
}
public ConcurrentRefHashMap(int initialCapacity) {
this(initialCapacity, ConcurrentHashMap.DEFAULT_LOAD_FACTOR);
}
private static final TObjectHashingStrategy THIS = new TObjectHashingStrategy() {
@Override
public int computeHashCode(Object object) {
throw new IncorrectOperationException();
}
@Override
public boolean equals(Object o1, Object o2) {
throw new IncorrectOperationException();
}
};
public ConcurrentRefHashMap(int initialCapacity, float loadFactor) {
this(initialCapacity, loadFactor, 4, THIS);
}
public ConcurrentRefHashMap(@NotNull final TObjectHashingStrategy<K> hashingStrategy) {
this(ConcurrentHashMap.DEFAULT_INITIAL_CAPACITY, ConcurrentHashMap.DEFAULT_LOAD_FACTOR, ConcurrentHashMap.DEFAULT_SEGMENTS, hashingStrategy);
}
public ConcurrentRefHashMap(int initialCapacity,
float loadFactor,
int concurrencyLevel,
@NotNull TObjectHashingStrategy<K> hashingStrategy) {
myMap = new ConcurrentHashMap<Key<K, V>, V>(initialCapacity, loadFactor, concurrencyLevel,
ConcurrentRefHashMap.<K, V>convertKToKeyK(hashingStrategy));
}
public ConcurrentRefHashMap(Map<? extends K, ? extends V> t) {
this(Math.max(2 * t.size(), 11), 0.75f);
putAll(t);
}
public ConcurrentRefHashMap(@NotNull final TObjectHashingStrategy<K> hashingStrategy) {
myMap = new ConcurrentHashMap<Key<K, V>, V>(ConcurrentRefHashMap.<K, V>convertKToKeyK(hashingStrategy));
myHashingStrategy = hashingStrategy == THIS ? this : hashingStrategy;
myMap = new ConcurrentHashMap<Key<K, V>, V>(initialCapacity, loadFactor, concurrencyLevel, CANONICAL);
}
@NotNull
@@ -184,18 +198,18 @@ abstract class ConcurrentRefHashMap<K, V> extends AbstractMap<K, V> implements C
private static final ThreadLocal<HardKey> HARD_KEY = new ThreadLocal<HardKey>() {
@Override
protected HardKey initialValue() {
return new HardKey(null, null);
return new HardKey();
}
};
private static <K, V> HardKey<K, V> createHardKey(K key) {
private HardKey<K, V> createHardKey(K key) {
HardKey hardKey = HARD_KEY.get();
hardKey.setKey(key);
hardKey.setKey(key, myHashingStrategy.computeHashCode(key));
return hardKey;
}
private static void releaseHardKey(HardKey key) {
key.setKey(null);
key.setKey(null, 0);
}
@Override
@@ -214,7 +228,7 @@ abstract class ConcurrentRefHashMap<K, V> extends AbstractMap<K, V> implements C
@Override
public V put(K key, V value) {
processQueue();
Key<K, V> weakKey = key == null ? NULL_KEY : createKey(key, value);
Key<K, V> weakKey = key == null ? NULL_KEY : createKey(key, value, myHashingStrategy);
return myMap.put(weakKey, value);
}
@@ -226,7 +240,7 @@ abstract class ConcurrentRefHashMap<K, V> extends AbstractMap<K, V> implements C
if (key == null) {
return myMap.remove(NULL_KEY);
}
HardKey hardKey = createHardKey(key);
HardKey hardKey = createHardKey((K)key);
V result = myMap.remove(hardKey);
releaseHardKey(hardKey);
return result;
@@ -238,13 +252,13 @@ abstract class ConcurrentRefHashMap<K, V> extends AbstractMap<K, V> implements C
myMap.clear();
}
private static class Entry<K, V> implements Map.Entry<K, V> {
private static class RefEntry<K, V> implements Map.Entry<K, V> {
private final Map.Entry<?, V> ent;
private final K key; /* Strong reference to key, so that the GC
private final K key; /* Strong reference to key, so that the GC
will leave it alone as long as this Entry
exists */
Entry(Map.Entry<?, V> ent, K key) {
RefEntry(Map.Entry<?, V> ent, K key) {
this.ent = ent;
this.key = key;
}
@@ -289,7 +303,7 @@ abstract class ConcurrentRefHashMap<K, V> extends AbstractMap<K, V> implements C
public Iterator<Map.Entry<K, V>> iterator() {
return new Iterator<Map.Entry<K, V>>() {
Iterator<Map.Entry<Key<K, V>, V>> hashIterator = hashEntrySet.iterator();
Entry<K, V> next = null;
RefEntry<K, V> next = null;
@Override
public boolean hasNext() {
@@ -301,7 +315,7 @@ abstract class ConcurrentRefHashMap<K, V> extends AbstractMap<K, V> implements C
/* Weak key has been cleared by GC */
continue;
}
next = new Entry<K, V>(ent, k);
next = new RefEntry<K, V>(ent, k);
return true;
}
return false;
@@ -312,7 +326,7 @@ abstract class ConcurrentRefHashMap<K, V> extends AbstractMap<K, V> implements C
if (next == null && !hasNext()) {
throw new NoSuchElementException();
}
Entry<K, V> e = next;
RefEntry<K, V> e = next;
next = null;
return e;
}
@@ -340,8 +354,8 @@ abstract class ConcurrentRefHashMap<K, V> extends AbstractMap<K, V> implements C
public boolean remove(Object o) {
processQueue();
if (!(o instanceof Map.Entry)) return false;
Map.Entry e = (Map.Entry)o;
Object ev = e.getValue();
Map.Entry<K,V> e = (Map.Entry)o;
V ev = e.getValue();
HardKey key = createHardKey(e.getKey());
@@ -380,25 +394,41 @@ abstract class ConcurrentRefHashMap<K, V> extends AbstractMap<K, V> implements C
@Override
public V putIfAbsent(@NotNull final K key, final V value) {
processQueue();
return myMap.putIfAbsent(createKey(key, value), value);
return myMap.putIfAbsent(createKey(key, value, myHashingStrategy), value);
}
@Override
public boolean remove(@NotNull final Object key, final Object value) {
processQueue();
return myMap.remove(createKey((K)key, (V)value), value);
return myMap.remove(createKey((K)key, (V)value, myHashingStrategy), value);
}
@Override
public boolean replace(@NotNull final K key, @NotNull final V oldValue, @NotNull final V newValue) {
processQueue();
return myMap.replace(createKey(key, oldValue), oldValue, newValue);
return myMap.replace(createKey(key, oldValue, myHashingStrategy), oldValue, newValue);
}
@Override
public V replace(@NotNull final K key, @NotNull final V value) {
processQueue();
return myMap.replace(createKey(key, value), value);
return myMap.replace(createKey(key, value, myHashingStrategy), value);
}
// MAKE SURE IT CONSISTENT WITH com.intellij.util.containers.ConcurrentHashMap
@Override
public int computeHashCode(final K object) {
int h = object.hashCode();
h += ~(h << 9);
h ^= (h >>> 14);
h += (h << 4);
h ^= (h >>> 10);
return h;
}
@Override
public boolean equals(final K o1, final K o2) {
return o1.equals(o2);
}
@TestOnly
@@ -19,6 +19,7 @@ package com.intellij.util.containers;
import gnu.trove.TObjectHashingStrategy;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.TestOnly;
import java.lang.ref.ReferenceQueue;
import java.util.*;
@@ -56,12 +57,16 @@ abstract class ConcurrentRefValueHashMap<K, V> implements ConcurrentMap<K, V> {
V get();
}
private void processQueue() {
boolean processQueue() {
boolean processed = false;
while (true) {
MyValueReference<K, V> ref = (MyValueReference<K, V>)myQueue.poll();
if (ref == null) break;
myMap.remove(ref.getKey(), ref);
processed = true;
}
return processed;
}
@Override
@@ -221,4 +226,9 @@ abstract class ConcurrentRefValueHashMap<K, V> implements ConcurrentMap<K, V> {
s += "] ";
return s;
}
@TestOnly
int underlyingMapSize() {
return myMap.size();
}
}
@@ -34,10 +34,10 @@ public final class ConcurrentSoftHashMap<K, V> extends ConcurrentRefHashMap<K, V
private final int myHash; // Hashcode of key, stored here since the key may be tossed by the GC
private final V value;
private SoftKey(@NotNull K k, V v, @NotNull ReferenceQueue<K> q) {
private SoftKey(@NotNull K k, final int hash, V v, @NotNull ReferenceQueue<K> q) {
super(k, q);
value = v;
myHash = k.hashCode();
myHash = hash;
}
@Override
@@ -61,8 +61,8 @@ public final class ConcurrentSoftHashMap<K, V> extends ConcurrentRefHashMap<K, V
}
@Override
protected ConcurrentRefHashMap.Key<K, V> createKey(@NotNull K key, V value) {
return new SoftKey<K, V>(key, value, myReferenceQueue);
protected ConcurrentRefHashMap.Key<K, V> createKey(@NotNull K key, V value, @NotNull TObjectHashingStrategy<K> hashingStrategy) {
return new SoftKey<K, V>(key, hashingStrategy.computeHashCode(key), value, myReferenceQueue);
}
public ConcurrentSoftHashMap(int initialCapacity, float loadFactor) {
@@ -37,10 +37,10 @@ public final class ConcurrentWeakHashMap<K, V> extends ConcurrentRefHashMap<K, V
private final int myHash; /* Hashcode of key, stored here since the key may be tossed by the GC */
private final V value;
private WeakKey(@NotNull K k, V v, ReferenceQueue<K> q) {
private WeakKey(@NotNull K k, final int hash, V v, ReferenceQueue<K> q) {
super(k, q);
value = v;
myHash = k.hashCode();
myHash = hash;
}
@Override
@@ -64,8 +64,8 @@ public final class ConcurrentWeakHashMap<K, V> extends ConcurrentRefHashMap<K, V
}
@Override
protected Key<K, V> createKey(@NotNull K key, V value) {
return new WeakKey<K, V>(key, value, myReferenceQueue);
protected Key<K, V> createKey(@NotNull K key, V value, @NotNull TObjectHashingStrategy<K> hashingStrategy) {
return new WeakKey<K, V>(key, hashingStrategy.computeHashCode(key), value, myReferenceQueue);
}
public ConcurrentWeakHashMap(int initialCapacity, float loadFactor) {
@@ -18,6 +18,8 @@ package com.intellij.util.containers;
import gnu.trove.TObjectHashingStrategy;
import junit.framework.TestCase;
import java.lang.ref.SoftReference;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -34,7 +36,7 @@ public class ConcurrentMapsTest extends TestCase {
assertEquals(1, map.underlyingMapSize());
}
public void testRemoveFromEntrySet() {
public void testRemoveFromSoftEntrySet() {
ConcurrentSoftHashMap<Object, Object> map = new ConcurrentSoftHashMap<Object, Object>();
map.put(this, this);
Set<Map.Entry<Object,Object>> entries = map.entrySet();
@@ -44,4 +46,76 @@ public class ConcurrentMapsTest extends TestCase {
assertTrue(map.isEmpty());
}
public void testRemoveFromWeakEntrySet() {
ConcurrentWeakHashMap<Object, Object> map = new ConcurrentWeakHashMap<Object, Object>();
map.put(this, this);
Set<Map.Entry<Object,Object>> entries = map.entrySet();
assertEquals(1, entries.size());
Map.Entry<Object, Object> entry = entries.iterator().next();
entries.remove(entry);
assertTrue(map.isEmpty());
}
public void testTossedWeakKeysAreRemoved() {
ConcurrentWeakHashMap<Object, Object> map = new ConcurrentWeakHashMap<Object, Object>();
map.put(new Object(), new Object());
do {
System.gc();
}
while (!map.processQueue());
assertEquals(0, map.underlyingMapSize());
map.put(this, this);
assertEquals(1, map.underlyingMapSize());
}
public static void tryGcSoftlyReachableObjects() {
SoftReference reference = new SoftReference(new Object());
List<Object> list = ContainerUtil.newArrayList();
while (reference.get() != null) {
list.add(new SoftReference<byte[]>(new byte[(int)Runtime.getRuntime().freeMemory() / 2]));
}
}
public void testTossedSoftKeysAreRemoved() {
ConcurrentSoftHashMap<Object, Object> map = new ConcurrentSoftHashMap<Object, Object>();
map.put(new Object(), new Object());
tryGcSoftlyReachableObjects();
do {
System.gc();
}
while (!map.processQueue());
assertEquals(0, map.underlyingMapSize());
map.put(this, this);
assertEquals(1, map.underlyingMapSize());
}
public void testTossedWeakValueIsRemoved() {
ConcurrentWeakValueHashMap<Object, Object> map = new ConcurrentWeakValueHashMap<Object, Object>();
map.put(new Object(), new Object());
do {
System.gc();
}
while (!map.processQueue());
assertEquals(0, map.underlyingMapSize());
map.put(this, this);
assertEquals(1, map.underlyingMapSize());
}
public void testTossedSoftValueIsRemoved() {
ConcurrentSoftValueHashMap<Object, Object> map = new ConcurrentSoftValueHashMap<Object, Object>();
map.put(new Object(), new Object());
tryGcSoftlyReachableObjects();
do {
System.gc();
}
while (!map.processQueue());
assertEquals(0, map.underlyingMapSize());
map.put(this, this);
assertEquals(1, map.underlyingMapSize());
}
}