diff --git a/platform/util/src/com/intellij/util/containers/ConcurrentRefHashMap.java b/platform/util/src/com/intellij/util/containers/ConcurrentRefHashMap.java index 8d7e0f74533b..49e6171a3c52 100644 --- a/platform/util/src/com/intellij/util/containers/ConcurrentRefHashMap.java +++ b/platform/util/src/com/intellij/util/containers/ConcurrentRefHashMap.java @@ -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 extends AbstractMap implements ConcurrentMap { +abstract class ConcurrentRefHashMap extends AbstractMap implements ConcurrentMap, TObjectHashingStrategy { + protected final ReferenceQueue myReferenceQueue = new ReferenceQueue(); + private final ConcurrentHashMap, V> myMap; // hashing strategy must be canonical, we compute corresponding hash codes using our own myHashingStrategy + @NotNull + private final TObjectHashingStrategy myHashingStrategy; + public interface Key { K get(); @@ -45,22 +51,16 @@ abstract class ConcurrentRefHashMap extends AbstractMap implements C int hashCode(); } - protected abstract Key createKey(@NotNull K key, V value); + protected abstract Key createKey(@NotNull K key, V value, @NotNull TObjectHashingStrategy hashingStrategy); private static class HardKey implements Key { 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 extends AbstractMap implements C @Override public V getValue() { - return value; + return null; } public boolean equals(Object o) { @@ -88,7 +88,6 @@ abstract class ConcurrentRefHashMap extends AbstractMap implements C } } - private final ConcurrentHashMap, V> myMap; private static final Key NULL_KEY = new Key() { @Override public Object get() { @@ -101,46 +100,61 @@ abstract class ConcurrentRefHashMap extends AbstractMap implements C } }; - protected final ReferenceQueue myReferenceQueue = new ReferenceQueue(); - + // returns true if some keys were processed boolean processQueue() { Key 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, V>(initialCapacity, loadFactor, 4); - } - - public ConcurrentRefHashMap(int initialCapacity) { - myMap = new ConcurrentHashMap, V>(initialCapacity); + public ConcurrentRefHashMap(Map t) { + this(Math.max(2 * t.size(), 11), ConcurrentHashMap.DEFAULT_LOAD_FACTOR); + putAll(t); } public ConcurrentRefHashMap() { - myMap = new ConcurrentHashMap, 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 hashingStrategy) { + this(ConcurrentHashMap.DEFAULT_INITIAL_CAPACITY, ConcurrentHashMap.DEFAULT_LOAD_FACTOR, ConcurrentHashMap.DEFAULT_SEGMENTS, hashingStrategy); } public ConcurrentRefHashMap(int initialCapacity, float loadFactor, int concurrencyLevel, @NotNull TObjectHashingStrategy hashingStrategy) { - myMap = new ConcurrentHashMap, V>(initialCapacity, loadFactor, concurrencyLevel, - ConcurrentRefHashMap.convertKToKeyK(hashingStrategy)); - } - - public ConcurrentRefHashMap(Map t) { - this(Math.max(2 * t.size(), 11), 0.75f); - putAll(t); - } - - public ConcurrentRefHashMap(@NotNull final TObjectHashingStrategy hashingStrategy) { - myMap = new ConcurrentHashMap, V>(ConcurrentRefHashMap.convertKToKeyK(hashingStrategy)); + myHashingStrategy = hashingStrategy == THIS ? this : hashingStrategy; + myMap = new ConcurrentHashMap, V>(initialCapacity, loadFactor, concurrencyLevel, CANONICAL); } @NotNull @@ -184,18 +198,18 @@ abstract class ConcurrentRefHashMap extends AbstractMap implements C private static final ThreadLocal HARD_KEY = new ThreadLocal() { @Override protected HardKey initialValue() { - return new HardKey(null, null); + return new HardKey(); } }; - private static HardKey createHardKey(K key) { + private HardKey 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 extends AbstractMap implements C @Override public V put(K key, V value) { processQueue(); - Key weakKey = key == null ? NULL_KEY : createKey(key, value); + Key weakKey = key == null ? NULL_KEY : createKey(key, value, myHashingStrategy); return myMap.put(weakKey, value); } @@ -226,7 +240,7 @@ abstract class ConcurrentRefHashMap extends AbstractMap 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 extends AbstractMap implements C myMap.clear(); } - private static class Entry implements Map.Entry { + private static class RefEntry implements Map.Entry { private final Map.Entry 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 ent, K key) { + RefEntry(Map.Entry ent, K key) { this.ent = ent; this.key = key; } @@ -289,7 +303,7 @@ abstract class ConcurrentRefHashMap extends AbstractMap implements C public Iterator> iterator() { return new Iterator>() { Iterator, V>> hashIterator = hashEntrySet.iterator(); - Entry next = null; + RefEntry next = null; @Override public boolean hasNext() { @@ -301,7 +315,7 @@ abstract class ConcurrentRefHashMap extends AbstractMap implements C /* Weak key has been cleared by GC */ continue; } - next = new Entry(ent, k); + next = new RefEntry(ent, k); return true; } return false; @@ -312,7 +326,7 @@ abstract class ConcurrentRefHashMap extends AbstractMap implements C if (next == null && !hasNext()) { throw new NoSuchElementException(); } - Entry e = next; + RefEntry e = next; next = null; return e; } @@ -340,8 +354,8 @@ abstract class ConcurrentRefHashMap extends AbstractMap 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 e = (Map.Entry)o; + V ev = e.getValue(); HardKey key = createHardKey(e.getKey()); @@ -380,25 +394,41 @@ abstract class ConcurrentRefHashMap extends AbstractMap 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 diff --git a/platform/util/src/com/intellij/util/containers/ConcurrentRefValueHashMap.java b/platform/util/src/com/intellij/util/containers/ConcurrentRefValueHashMap.java index e8bfd7d2577e..41f3d9c2af79 100644 --- a/platform/util/src/com/intellij/util/containers/ConcurrentRefValueHashMap.java +++ b/platform/util/src/com/intellij/util/containers/ConcurrentRefValueHashMap.java @@ -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 implements ConcurrentMap { V get(); } - private void processQueue() { + boolean processQueue() { + boolean processed = false; + while (true) { MyValueReference ref = (MyValueReference)myQueue.poll(); if (ref == null) break; myMap.remove(ref.getKey(), ref); + processed = true; } + return processed; } @Override @@ -221,4 +226,9 @@ abstract class ConcurrentRefValueHashMap implements ConcurrentMap { s += "] "; return s; } + + @TestOnly + int underlyingMapSize() { + return myMap.size(); + } } diff --git a/platform/util/src/com/intellij/util/containers/ConcurrentSoftHashMap.java b/platform/util/src/com/intellij/util/containers/ConcurrentSoftHashMap.java index bd7954fab5b0..0bd2122d4e67 100644 --- a/platform/util/src/com/intellij/util/containers/ConcurrentSoftHashMap.java +++ b/platform/util/src/com/intellij/util/containers/ConcurrentSoftHashMap.java @@ -34,10 +34,10 @@ public final class ConcurrentSoftHashMap extends ConcurrentRefHashMap q) { + private SoftKey(@NotNull K k, final int hash, V v, @NotNull ReferenceQueue q) { super(k, q); value = v; - myHash = k.hashCode(); + myHash = hash; } @Override @@ -61,8 +61,8 @@ public final class ConcurrentSoftHashMap extends ConcurrentRefHashMap createKey(@NotNull K key, V value) { - return new SoftKey(key, value, myReferenceQueue); + protected ConcurrentRefHashMap.Key createKey(@NotNull K key, V value, @NotNull TObjectHashingStrategy hashingStrategy) { + return new SoftKey(key, hashingStrategy.computeHashCode(key), value, myReferenceQueue); } public ConcurrentSoftHashMap(int initialCapacity, float loadFactor) { diff --git a/platform/util/src/com/intellij/util/containers/ConcurrentWeakHashMap.java b/platform/util/src/com/intellij/util/containers/ConcurrentWeakHashMap.java index 4ae668421808..44c5408cb6ec 100644 --- a/platform/util/src/com/intellij/util/containers/ConcurrentWeakHashMap.java +++ b/platform/util/src/com/intellij/util/containers/ConcurrentWeakHashMap.java @@ -37,10 +37,10 @@ public final class ConcurrentWeakHashMap extends ConcurrentRefHashMap q) { + private WeakKey(@NotNull K k, final int hash, V v, ReferenceQueue q) { super(k, q); value = v; - myHash = k.hashCode(); + myHash = hash; } @Override @@ -64,8 +64,8 @@ public final class ConcurrentWeakHashMap extends ConcurrentRefHashMap createKey(@NotNull K key, V value) { - return new WeakKey(key, value, myReferenceQueue); + protected Key createKey(@NotNull K key, V value, @NotNull TObjectHashingStrategy hashingStrategy) { + return new WeakKey(key, hashingStrategy.computeHashCode(key), value, myReferenceQueue); } public ConcurrentWeakHashMap(int initialCapacity, float loadFactor) { diff --git a/platform/util/testSrc/com/intellij/util/containers/ConcurrentMapsTest.java b/platform/util/testSrc/com/intellij/util/containers/ConcurrentMapsTest.java index f048b9a9a907..b89ef901d045 100644 --- a/platform/util/testSrc/com/intellij/util/containers/ConcurrentMapsTest.java +++ b/platform/util/testSrc/com/intellij/util/containers/ConcurrentMapsTest.java @@ -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 map = new ConcurrentSoftHashMap(); map.put(this, this); Set> entries = map.entrySet(); @@ -44,4 +46,76 @@ public class ConcurrentMapsTest extends TestCase { assertTrue(map.isEmpty()); } + + public void testRemoveFromWeakEntrySet() { + ConcurrentWeakHashMap map = new ConcurrentWeakHashMap(); + map.put(this, this); + Set> entries = map.entrySet(); + assertEquals(1, entries.size()); + Map.Entry entry = entries.iterator().next(); + entries.remove(entry); + + assertTrue(map.isEmpty()); + } + + public void testTossedWeakKeysAreRemoved() { + ConcurrentWeakHashMap map = new ConcurrentWeakHashMap(); + 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 list = ContainerUtil.newArrayList(); + while (reference.get() != null) { + list.add(new SoftReference(new byte[(int)Runtime.getRuntime().freeMemory() / 2])); + } + } + + public void testTossedSoftKeysAreRemoved() { + ConcurrentSoftHashMap map = new ConcurrentSoftHashMap(); + 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 map = new ConcurrentWeakValueHashMap(); + 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 map = new ConcurrentSoftValueHashMap(); + 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()); + } }