/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.lucene.util; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Random; import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; public class TestWeakIdentityMap extends LuceneTestCase { public void testSimpleHashMap() { final WeakIdentityMap<String,String> map = WeakIdentityMap.newHashMap(random().nextBoolean()); // we keep strong references to the keys, // so WeakIdentityMap will not forget about them: String key1 = new String("foo"); String key2 = new String("foo"); String key3 = new String("foo"); assertNotSame(key1, key2); assertEquals(key1, key2); assertNotSame(key1, key3); assertEquals(key1, key3); assertNotSame(key2, key3); assertEquals(key2, key3); // try null key & check its iterator also return null: map.put(null, "null"); { Iterator<String> it = map.keyIterator(); assertTrue(it.hasNext()); assertNull(it.next()); assertFalse(it.hasNext()); assertFalse(it.hasNext()); } // 2 more keys: map.put(key1, "bar1"); map.put(key2, "bar2"); assertEquals(3, map.size()); assertEquals("bar1", map.get(key1)); assertEquals("bar2", map.get(key2)); assertEquals(null, map.get(key3)); assertEquals("null", map.get(null)); assertTrue(map.containsKey(key1)); assertTrue(map.containsKey(key2)); assertFalse(map.containsKey(key3)); assertTrue(map.containsKey(null)); // repeat and check that we have no double entries map.put(key1, "bar1"); map.put(key2, "bar2"); map.put(null, "null"); assertEquals(3, map.size()); assertEquals("bar1", map.get(key1)); assertEquals("bar2", map.get(key2)); assertEquals(null, map.get(key3)); assertEquals("null", map.get(null)); assertTrue(map.containsKey(key1)); assertTrue(map.containsKey(key2)); assertFalse(map.containsKey(key3)); assertTrue(map.containsKey(null)); map.remove(null); assertEquals(2, map.size()); map.remove(key1); assertEquals(1, map.size()); map.put(key1, "bar1"); map.put(key2, "bar2"); map.put(key3, "bar3"); assertEquals(3, map.size()); int c = 0, keysAssigned = 0; for (Iterator<String> it = map.keyIterator(); it.hasNext();) { assertTrue(it.hasNext()); // try again, should return same result! final String k = it.next(); assertTrue(k == key1 || k == key2 | k == key3); keysAssigned += (k == key1) ? 1 : ((k == key2) ? 2 : 4); c++; } assertEquals(3, c); assertEquals("all keys must have been seen", 1+2+4, keysAssigned); c = 0; for (Iterator<String> it = map.valueIterator(); it.hasNext();) { final String v = it.next(); assertTrue(v.startsWith("bar")); c++; } assertEquals(3, c); // clear strong refs key1 = key2 = key3 = null; // check that GC does not cause problems in reap() method, wait 1 second and let GC work: int size = map.size(); for (int i = 0; size > 0 && i < 10; i++) try { System.runFinalization(); System.gc(); int newSize = map.size(); assertTrue("previousSize("+size+")>=newSize("+newSize+")", size >= newSize); size = newSize; Thread.sleep(100L); c = 0; for (Iterator<String> it = map.keyIterator(); it.hasNext();) { assertNotNull(it.next()); c++; } newSize = map.size(); assertTrue("previousSize("+size+")>=iteratorSize("+c+")", size >= c); assertTrue("iteratorSize("+c+")>=newSize("+newSize+")", c >= newSize); size = newSize; } catch (InterruptedException ie) {} map.clear(); assertEquals(0, map.size()); assertTrue(map.isEmpty()); Iterator<String> it = map.keyIterator(); assertFalse(it.hasNext()); expectThrows(NoSuchElementException.class, () -> { it.next(); }); key1 = new String("foo"); key2 = new String("foo"); map.put(key1, "bar1"); map.put(key2, "bar2"); assertEquals(2, map.size()); map.clear(); assertEquals(0, map.size()); assertTrue(map.isEmpty()); } public void testConcurrentHashMap() throws Exception { // don't make threadCount and keyCount random, otherwise easily OOMs or fails otherwise: final int threadCount = 8, keyCount = 1024; final ExecutorService exec = Executors.newFixedThreadPool(threadCount, new NamedThreadFactory("testConcurrentHashMap")); final WeakIdentityMap<Object,Integer> map = WeakIdentityMap.newConcurrentHashMap(random().nextBoolean()); // we keep strong references to the keys, // so WeakIdentityMap will not forget about them: final AtomicReferenceArray<Object> keys = new AtomicReferenceArray<>(keyCount); for (int j = 0; j < keyCount; j++) { keys.set(j, new Object()); } try { for (int t = 0; t < threadCount; t++) { final Random rnd = new Random(random().nextLong()); exec.execute(new Runnable() { @Override public void run() { final int count = atLeast(rnd, 10000); for (int i = 0; i < count; i++) { final int j = rnd.nextInt(keyCount); switch (rnd.nextInt(5)) { case 0: map.put(keys.get(j), Integer.valueOf(j)); break; case 1: final Integer v = map.get(keys.get(j)); if (v != null) { assertEquals(j, v.intValue()); } break; case 2: map.remove(keys.get(j)); break; case 3: // renew key, the old one will be GCed at some time: keys.set(j, new Object()); break; case 4: // check iterator still working for (Iterator<Object> it = map.keyIterator(); it.hasNext();) { assertNotNull(it.next()); } break; default: fail("Should not get here."); } } } }); } } finally { exec.shutdown(); while (!exec.awaitTermination(1000L, TimeUnit.MILLISECONDS)); } // clear strong refs for (int j = 0; j < keyCount; j++) { keys.set(j, null); } // check that GC does not cause problems in reap() method: int size = map.size(); for (int i = 0; size > 0 && i < 10; i++) try { System.runFinalization(); System.gc(); int newSize = map.size(); assertTrue("previousSize("+size+")>=newSize("+newSize+")", size >= newSize); size = newSize; Thread.sleep(100L); int c = 0; for (Iterator<Object> it = map.keyIterator(); it.hasNext();) { assertNotNull(it.next()); c++; } newSize = map.size(); assertTrue("previousSize("+size+")>=iteratorSize("+c+")", size >= c); assertTrue("iteratorSize("+c+")>=newSize("+newSize+")", c >= newSize); size = newSize; } catch (InterruptedException ie) {} } }