mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
fixed inconsistent hash calculation
This commit is contained in:
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user