diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/types/CopyOnWriteArrayMap.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/types/CopyOnWriteArrayMap.java index bc184b1098a..1b9ea09f31d 100644 --- a/hbase-common/src/main/java/org/apache/hadoop/hbase/types/CopyOnWriteArrayMap.java +++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/types/CopyOnWriteArrayMap.java @@ -332,7 +332,8 @@ public class CopyOnWriteArrayMap extends AbstractMap if (index < 0) { COWEntry newEntry = new COWEntry<>(key, value); this.holder = current.insert(-(index + 1), newEntry); - return value; + // putIfAbsent contract requires returning null if no previous entry exists + return null; } return current.entries[index].getValue(); } diff --git a/hbase-common/src/test/java/org/apache/hadoop/hbase/types/TestCopyOnWriteMaps.java b/hbase-common/src/test/java/org/apache/hadoop/hbase/types/TestCopyOnWriteMaps.java index 7c8a7d2f4b6..5487997afea 100644 --- a/hbase-common/src/test/java/org/apache/hadoop/hbase/types/TestCopyOnWriteMaps.java +++ b/hbase-common/src/test/java/org/apache/hadoop/hbase/types/TestCopyOnWriteMaps.java @@ -41,6 +41,7 @@ public class TestCopyOnWriteMaps { public static final HBaseClassTestRule CLASS_RULE = HBaseClassTestRule.forClass(TestCopyOnWriteMaps.class); + private static final int MAX_INITIAL_ENTRIES = 10_000; private static final int MAX_RAND = 10 * 1000 * 1000; private ConcurrentNavigableMap m; private ConcurrentSkipListMap csm; @@ -50,7 +51,7 @@ public class TestCopyOnWriteMaps { m = new CopyOnWriteArrayMap<>(); csm = new ConcurrentSkipListMap<>(); - for (long i = 0; i < 10000; i++) { + for (long i = 0; i < MAX_INITIAL_ENTRIES; i++) { long o = ThreadLocalRandom.current().nextLong(MAX_RAND); m.put(i, o); csm.put(i, o); @@ -60,6 +61,18 @@ public class TestCopyOnWriteMaps { csm.put(0L, o); } + @Test + public void testPutIfAbsent() { + long key = MAX_INITIAL_ENTRIES * 2; + long val = ThreadLocalRandom.current().nextLong(MAX_RAND); + // initial call should return null, then should return previous value + assertNull(m.putIfAbsent(key, val)); + assertEquals(val, (long) m.putIfAbsent(key, val * 2)); + // test same with csm so we ensure similar semantics + assertNull(csm.putIfAbsent(key, val)); + assertEquals(val, (long) csm.putIfAbsent(key, val * 2)); + } + @Test public void testSize() { assertEquals("Size should always be equal", m.size(), csm.size());