Repository: hbase
Updated Branches:
  refs/heads/branch-1 604c9b2cc -> 4d5ac316b


HBASE-14708 Use copy on write Map for region location cache

Summary:
Create and use a copy on write map for region location.

  - Create a copy on write map backed by a sorted array.
  - Create a test for both comparing each with a jdk provided map.
  - Change MetaCache to use the new map.

Test Plan:
  - org.apache.hadoop.hbase.client.TestFromClientSide
  - TestHCM

Differential Revision: https://reviews.facebook.net/D49545


Project: http://git-wip-us.apache.org/repos/asf/hbase/repo
Commit: http://git-wip-us.apache.org/repos/asf/hbase/commit/4d5ac316
Tree: http://git-wip-us.apache.org/repos/asf/hbase/tree/4d5ac316
Diff: http://git-wip-us.apache.org/repos/asf/hbase/diff/4d5ac316

Branch: refs/heads/branch-1
Commit: 4d5ac316b626baa43880a1b65916e008e3d73b69
Parents: 604c9b2
Author: Elliott Clark <ecl...@apache.org>
Authored: Tue Oct 27 12:16:37 2015 -0700
Committer: Elliott Clark <ecl...@apache.org>
Committed: Tue Nov 17 11:01:52 2015 -0800

----------------------------------------------------------------------
 .../apache/hadoop/hbase/MetaTableAccessor.java  |   4 +-
 .../apache/hadoop/hbase/client/MetaCache.java   |  25 +-
 .../hadoop/hbase/types/CopyOnWriteArrayMap.java | 974 +++++++++++++++++++
 .../hadoop/hbase/types/TestCopyOnWriteMaps.java | 311 ++++++
 4 files changed, 1299 insertions(+), 15 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/hbase/blob/4d5ac316/hbase-client/src/main/java/org/apache/hadoop/hbase/MetaTableAccessor.java
----------------------------------------------------------------------
diff --git 
a/hbase-client/src/main/java/org/apache/hadoop/hbase/MetaTableAccessor.java 
b/hbase-client/src/main/java/org/apache/hadoop/hbase/MetaTableAccessor.java
index 0823094..6626485 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/MetaTableAccessor.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/MetaTableAccessor.java
@@ -740,7 +740,9 @@ public class MetaTableAccessor {
     // iterate until all serverName columns are seen
     int replicaId = 0;
     byte[] serverColumn = getServerColumn(replicaId);
-    SortedMap<byte[], byte[]> serverMap = infoMap.tailMap(serverColumn, false);
+    SortedMap<byte[], byte[]> serverMap = null;
+    serverMap = infoMap.tailMap(serverColumn, false);
+
     if (serverMap.isEmpty()) return new RegionLocations(locations);
 
     for (Map.Entry<byte[], byte[]> entry : serverMap.entrySet()) {

http://git-wip-us.apache.org/repos/asf/hbase/blob/4d5ac316/hbase-client/src/main/java/org/apache/hadoop/hbase/client/MetaCache.java
----------------------------------------------------------------------
diff --git 
a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/MetaCache.java 
b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/MetaCache.java
index a9ce173..c9f5e02 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/MetaCache.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/MetaCache.java
@@ -21,10 +21,9 @@ package org.apache.hadoop.hbase.client;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.ConcurrentSkipListMap;
-import java.util.concurrent.ConcurrentSkipListSet;
+import java.util.concurrent.ConcurrentNavigableMap;
+import java.util.concurrent.CopyOnWriteArraySet;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.hadoop.hbase.classification.InterfaceAudience;
@@ -36,6 +35,7 @@ import org.apache.hadoop.hbase.KeyValue.KVComparator;
 import org.apache.hadoop.hbase.RegionLocations;
 import org.apache.hadoop.hbase.ServerName;
 import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.types.CopyOnWriteArrayMap;
 import org.apache.hadoop.hbase.util.Bytes;
 
 /**
@@ -49,16 +49,16 @@ public class MetaCache {
   /**
    * Map of table to table {@link HRegionLocation}s.
    */
-  private final ConcurrentMap<TableName, ConcurrentSkipListMap<byte[], 
RegionLocations>>
+  private final ConcurrentMap<TableName, ConcurrentNavigableMap<byte[], 
RegionLocations>>
   cachedRegionLocations =
-  new ConcurrentHashMap<TableName, ConcurrentSkipListMap<byte[], 
RegionLocations>>();
+  new CopyOnWriteArrayMap<>();
 
   // The presence of a server in the map implies it's likely that there is an
   // entry in cachedRegionLocations that map to this server; but the absence
   // of a server in this map guarentees that there is no entry in cache that
   // maps to the absent server.
   // The access to this attribute must be protected by a lock on 
cachedRegionLocations
-  private final Set<ServerName> cachedServers = new 
ConcurrentSkipListSet<ServerName>();
+  private final Set<ServerName> cachedServers = new CopyOnWriteArraySet<>();
 
   private final MetricsConnection metrics;
 
@@ -70,13 +70,10 @@ public class MetaCache {
    * Search the cache for a location that fits our table and row key.
    * Return null if no suitable region is located.
    *
-   *
-   * @param tableName
-   * @param row
    * @return Null or region location found in cache.
    */
   public RegionLocations getCachedLocation(final TableName tableName, final 
byte [] row) {
-    ConcurrentSkipListMap<byte[], RegionLocations> tableLocations =
+    ConcurrentNavigableMap<byte[], RegionLocations> tableLocations =
       getTableLocations(tableName);
 
     Entry<byte[], RegionLocations> e = tableLocations.floorEntry(row);
@@ -192,15 +189,15 @@ public class MetaCache {
    * @param tableName
    * @return Map of cached locations for passed <code>tableName</code>
    */
-  private ConcurrentSkipListMap<byte[], RegionLocations>
+  private ConcurrentNavigableMap<byte[], RegionLocations>
     getTableLocations(final TableName tableName) {
     // find the map of cached locations for this table
-    ConcurrentSkipListMap<byte[], RegionLocations> result;
+    ConcurrentNavigableMap<byte[], RegionLocations> result;
     result = this.cachedRegionLocations.get(tableName);
     // if tableLocations for this table isn't built yet, make one
     if (result == null) {
-      result = new ConcurrentSkipListMap<byte[], 
RegionLocations>(Bytes.BYTES_COMPARATOR);
-      ConcurrentSkipListMap<byte[], RegionLocations> old =
+      result = new CopyOnWriteArrayMap<>(Bytes.BYTES_COMPARATOR);
+      ConcurrentNavigableMap<byte[], RegionLocations> old =
           this.cachedRegionLocations.putIfAbsent(tableName, result);
       if (old != null) {
         return old;

http://git-wip-us.apache.org/repos/asf/hbase/blob/4d5ac316/hbase-common/src/main/java/org/apache/hadoop/hbase/types/CopyOnWriteArrayMap.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..41056b2
--- /dev/null
+++ 
b/hbase-common/src/main/java/org/apache/hadoop/hbase/types/CopyOnWriteArrayMap.java
@@ -0,0 +1,974 @@
+/**
+ * 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.hadoop.hbase.types;
+
+import org.apache.hadoop.hbase.classification.InterfaceAudience;
+import org.apache.hadoop.hbase.classification.InterfaceStability;
+
+import java.util.AbstractMap;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NavigableSet;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.concurrent.ConcurrentNavigableMap;
+
+/**
+ * A Map that keeps a sorted array in order to provide the concurrent map 
interface.
+ * Keeping a sorted array means that it's much more cache line friendly, 
making reads faster
+ * than the tree version.
+ *
+ * In order to make concurrent reads and writes safe this does a copy on write.
+ * There can only be one concurrent write at a time.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Stable
+public class CopyOnWriteArrayMap<K, V> extends AbstractMap<K, V>
+    implements Map<K, V>, ConcurrentNavigableMap<K, V> {
+  private final Comparator<? super K> keyComparator;
+  private volatile ArrayHolder<K, V> holder;
+
+  public CopyOnWriteArrayMap() {
+    this(new Comparator<K>() {
+      @Override
+      public int compare(K o1, K o2) {
+        return ((Comparable) o1).compareTo(o2);
+      }
+    });
+  }
+
+  public CopyOnWriteArrayMap(final Comparator<? super K> keyComparator) {
+    this.keyComparator = keyComparator;
+    this.holder = new ArrayHolder<>(keyComparator, new Comparator<Entry<K, 
V>>() {
+      @Override
+      public int compare(Entry<K, V> o1, Entry<K, V> o2) {
+        return keyComparator.compare(o1.getKey(), o2.getKey());
+      }
+    });
+  }
+
+  private CopyOnWriteArrayMap(final Comparator<? super K> keyComparator, 
ArrayHolder<K, V> holder) {
+    this.keyComparator = keyComparator;
+    this.holder = holder;
+  }
+
+  /*
+    Un synchronized read operations.
+
+    No locking.
+    No waiting
+    No copying.
+
+    These should all be FAST.
+   */
+
+  @Override
+  public Comparator<? super K> comparator() {
+    return keyComparator;
+  }
+
+  @Override
+  public ConcurrentNavigableMap<K, V> tailMap(K fromKey, boolean inclusive) {
+    ArrayHolder<K, V> current = this.holder;
+    int index = current.find(fromKey);
+
+    if (!inclusive && index >= 0) {
+      index++;
+    } else if (index < 0) {
+      index = -(index + 1);
+    }
+    return new CopyOnWriteArrayMap<>(
+        this.keyComparator,
+        new ArrayHolder<>(
+            current.entries,
+            index,
+            current.endIndex,
+            current.keyComparator,
+            current.comparator));
+  }
+
+  @Override
+  public ConcurrentNavigableMap<K, V> tailMap(K fromKey) {
+    return this.tailMap(fromKey, true);
+  }
+
+  @Override
+  public K firstKey() {
+    ArrayHolder<K, V> current = this.holder;
+    if (current.getLength() == 0) {
+      return null;
+    }
+    return current.entries[current.startIndex].getKey();
+  }
+
+  @Override
+  public K lastKey() {
+    ArrayHolder<K, V> current = this.holder;
+    if (current.getLength() == 0) {
+      return null;
+    }
+    return current.entries[current.endIndex - 1].getKey();
+  }
+
+  @Override
+  public Entry<K, V> lowerEntry(K key) {
+    ArrayHolder<K, V> current = this.holder;
+    if (current.getLength() == 0) {
+      return null;
+    }
+
+    int index = current.find(key);
+
+    // There's a key exactly equal.
+    if (index >= 0) {
+      index -= 1;
+    } else {
+      index = -(index + 1) - 1;
+    }
+
+    if (index < current.startIndex || index >= current.endIndex) {
+      return null;
+    }
+    return current.entries[index];
+  }
+
+  @Override
+  public K lowerKey(K key) {
+    Map.Entry<K, V> entry = lowerEntry(key);
+    if (entry == null) {
+      return null;
+    }
+    return entry.getKey();
+  }
+
+  @Override
+  public Entry<K, V> floorEntry(K key) {
+    ArrayHolder<K, V> current = this.holder;
+    if (current.getLength() == 0) {
+      return null;
+    }
+    int index = current.find(key);
+    if (index < 0) {
+      index = -(index + 1) - 1;
+    }
+    if (index < current.startIndex || index >= current.endIndex) {
+      return null;
+    }
+
+    return current.entries[index];
+  }
+
+  @Override
+  public K floorKey(K key) {
+    Map.Entry<K, V> entry = floorEntry(key);
+    if (entry == null) {
+      return null;
+    }
+    return entry.getKey();
+  }
+
+  @Override
+  public Entry<K, V> ceilingEntry(K key) {
+    ArrayHolder<K, V> current = this.holder;
+    if (current.getLength() == 0) {
+      return null;
+    }
+    int index = current.find(key);
+    if (index < 0) {
+      index = -(index + 1);
+    }
+    if (index < current.startIndex || index >= current.endIndex) {
+      return null;
+    }
+
+    return current.entries[index];
+  }
+
+  @Override
+  public K ceilingKey(K key) {
+    Map.Entry<K, V> entry = ceilingEntry(key);
+    if (entry == null) {
+      return null;
+    }
+    return entry.getKey();
+  }
+
+  @Override
+  public Entry<K, V> higherEntry(K key) {
+    ArrayHolder<K, V> current = this.holder;
+    if (current.getLength() == 0) {
+      return null;
+    }
+    int index = current.find(key);
+
+    // There's a key exactly equal.
+    if (index >= 0) {
+      index += 1;
+    } else {
+      index = -(index + 1);
+    }
+
+    if (index < current.startIndex || index >= current.endIndex) {
+      return null;
+    }
+    return current.entries[index];
+  }
+
+  @Override
+  public K higherKey(K key) {
+    Map.Entry<K, V> entry = higherEntry(key);
+    if (entry == null) {
+      return null;
+    }
+    return entry.getKey();
+  }
+
+  @Override
+  public Entry<K, V> firstEntry() {
+    ArrayHolder<K, V> current = this.holder;
+    if (current.getLength() == 0) {
+      return null;
+    }
+    return current.entries[current.startIndex];
+  }
+
+  @Override
+  public Entry<K, V> lastEntry() {
+    ArrayHolder<K, V> current = this.holder;
+    if (current.getLength() == 0) {
+      return null;
+    }
+    return current.entries[current.endIndex - 1];
+  }
+
+  @Override
+  public int size() {
+    return holder.getLength();
+  }
+
+  @Override
+  public boolean isEmpty() {
+    return holder.getLength() == 0;
+  }
+
+  @Override
+  public boolean containsKey(Object key) {
+    ArrayHolder<K, V> current = this.holder;
+    int index = current.find((K) key);
+    return index >= 0;
+  }
+
+  @Override
+  public V get(Object key) {
+
+    ArrayHolder<K, V> current = this.holder;
+    int index = current.find((K) key);
+    if (index >= 0) {
+      return current.entries[index].getValue();
+    }
+    return null;
+  }
+
+  @Override
+  public NavigableSet<K> keySet() {
+    return new ArrayKeySet<>(this.holder);
+  }
+
+  @Override
+  public Collection<V> values() {
+    return new ArrayValueCollection<>(this.holder);
+  }
+
+  @Override
+  public Set<Entry<K, V>> entrySet() {
+    return new ArrayEntrySet<>(this.holder);
+  }
+
+  /*
+     Synchronized write methods.
+
+     Every method should be synchronized.
+     Only one modification at a time.
+
+     These will be slow.
+   */
+
+
+  @Override
+  public synchronized V put(K key, V value) {
+    ArrayHolder<K, V> current = this.holder;
+    int index = current.find(key);
+    COWEntry<K, V> newEntry = new COWEntry<>(key, value);
+    if (index >= 0) {
+      this.holder = current.replace(index, newEntry);
+      return current.entries[index].getValue();
+    } else {
+      this.holder = current.insert(-(index + 1), newEntry);
+    }
+    return null;
+  }
+
+  @Override
+  public synchronized V remove(Object key) {
+
+    ArrayHolder<K, V> current = this.holder;
+    int index = current.find((K) key);
+    if (index >= 0) {
+      this.holder = current.remove(index);
+      return current.entries[index].getValue();
+    }
+    return null;
+  }
+
+  @Override
+  public synchronized void clear() {
+    this.holder = new ArrayHolder<>(this.holder.keyComparator, 
this.holder.comparator);
+  }
+
+  @Override
+  public synchronized V putIfAbsent(K key, V value) {
+    ArrayHolder<K, V> current = this.holder;
+    int index = current.find(key);
+
+    if (index < 0) {
+      COWEntry<K, V> newEntry = new COWEntry<>(key, value);
+      this.holder = current.insert(-(index + 1), newEntry);
+      return value;
+    }
+    return current.entries[index].getValue();
+  }
+
+  @Override
+  public synchronized boolean remove(Object key, Object value) {
+    ArrayHolder<K, V> current = this.holder;
+    int index = current.find((K) key);
+
+    if (index >= 0 && current.entries[index].getValue().equals(value)) {
+      this.holder = current.remove(index);
+      return true;
+    }
+    return false;
+  }
+
+  @Override
+  public synchronized boolean replace(K key, V oldValue, V newValue) {
+    ArrayHolder<K, V> current = this.holder;
+    int index = current.find(key);
+
+    if (index >= 0 && current.entries[index].getValue().equals(oldValue)) {
+      COWEntry<K, V> newEntry = new COWEntry<>(key, newValue);
+      this.holder = current.replace(index, newEntry);
+      return true;
+    }
+    return false;
+  }
+
+  @Override
+  public synchronized V replace(K key, V value) {
+    ArrayHolder<K, V> current = this.holder;
+    int index = current.find(key);
+
+    if (index >= 0) {
+      COWEntry<K, V> newEntry = new COWEntry<>(key, value);
+      this.holder = current.replace(index, newEntry);
+      return current.entries[index].getValue();
+    }
+    return null;
+  }
+
+  @Override
+  public Entry<K, V> pollFirstEntry() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Entry<K, V> pollLastEntry() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public ConcurrentNavigableMap<K, V> descendingMap() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public NavigableSet<K> navigableKeySet() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public ConcurrentNavigableMap<K, V> subMap(K fromKey, K toKey) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public ConcurrentNavigableMap<K, V> headMap(K toKey) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public ConcurrentNavigableMap<K, V> subMap(K fromKey,
+                                             boolean fromInclusive,
+                                             K toKey,
+                                             boolean toInclusive) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public ConcurrentNavigableMap<K, V> headMap(K toKey, boolean inclusive) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public NavigableSet<K> descendingKeySet() {
+    throw new UnsupportedOperationException();
+  }
+
+  private final class ArrayKeySet<K, V> implements NavigableSet<K> {
+
+    private final ArrayHolder<K, V> holder;
+
+    private ArrayKeySet(ArrayHolder<K, V> holder) {
+      this.holder = holder;
+    }
+
+    @Override
+    public int size() {
+      return holder.getLength();
+    }
+
+    @Override
+    public boolean isEmpty() {
+      return holder.getLength() == 0;
+    }
+
+    @Override
+    public boolean contains(Object o) {
+      ArrayHolder<K, V> current = this.holder;
+
+      for (int i = current.startIndex; i < current.endIndex; i++) {
+        if (current.entries[i].getValue().equals(o)) {
+          return true;
+        }
+      }
+      return false;
+    }
+
+    @Override
+    public K lower(K k) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public K floor(K k) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public K ceiling(K k) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public K higher(K k) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public K pollFirst() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public K pollLast() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Iterator<K> iterator() {
+      return new ArrayKeyIterator<>(this.holder);
+    }
+
+    @Override
+    public NavigableSet<K> descendingSet() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Iterator<K> descendingIterator() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public NavigableSet<K> subSet(K fromElement,
+                                  boolean fromInclusive,
+                                  K toElement,
+                                  boolean toInclusive) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public NavigableSet<K> headSet(K toElement, boolean inclusive) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public NavigableSet<K> tailSet(K fromElement, boolean inclusive) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Comparator<? super K> comparator() {
+      return (Comparator<? super K>) keyComparator;
+    }
+
+    @Override
+    public SortedSet<K> subSet(K fromElement, K toElement) {
+      return null;
+    }
+
+    @Override
+    public SortedSet<K> headSet(K toElement) {
+      return null;
+    }
+
+    @Override
+    public SortedSet<K> tailSet(K fromElement) {
+      return null;
+    }
+
+    @Override
+    public K first() {
+      ArrayHolder<K, V> current = this.holder;
+      if (current.getLength() == 0) {
+        return null;
+      }
+      return current.entries[current.startIndex].getKey();
+    }
+
+    @Override
+    public K last() {
+      ArrayHolder<K, V> current = this.holder;
+      if (current.getLength() == 0) {
+        return null;
+      }
+      return current.entries[current.endIndex - 1].getKey();
+    }
+
+    @Override
+    public Object[] toArray() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <T> T[] toArray(T[] a) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean add(K k) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean remove(Object o) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean containsAll(Collection<?> c) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean addAll(Collection<? extends K> c) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean retainAll(Collection<?> c) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean removeAll(Collection<?> c) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void clear() {
+      throw new UnsupportedOperationException();
+    }
+  }
+
+  private final class ArrayValueCollection<K, V> implements Collection<V> {
+
+    private final ArrayHolder<K, V> holder;
+
+    private ArrayValueCollection(ArrayHolder<K, V> holder) {
+      this.holder = holder;
+    }
+
+    @Override
+    public int size() {
+      return holder.getLength();
+    }
+
+    @Override
+    public boolean isEmpty() {
+      return holder.getLength() == 0;
+    }
+
+    @Override
+    public boolean contains(Object o) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Iterator<V> iterator() {
+      return new ArrayValueIterator<>(this.holder);
+    }
+
+    @Override
+    public Object[] toArray() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <T> T[] toArray(T[] a) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean add(V v) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean remove(Object o) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean containsAll(Collection<?> c) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean addAll(Collection<? extends V> c) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean removeAll(Collection<?> c) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean retainAll(Collection<?> c) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void clear() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      return false;
+    }
+
+    @Override
+    public int hashCode() {
+      return 0;
+    }
+  }
+
+  private final class ArrayKeyIterator<K, V> implements Iterator<K> {
+    int index;
+    private final ArrayHolder<K, V> holder;
+
+    private ArrayKeyIterator(ArrayHolder<K, V> holder) {
+      this.holder = holder;
+      index = holder.startIndex;
+    }
+
+
+    @Override
+    public boolean hasNext() {
+      return index < holder.endIndex;
+    }
+
+    @Override
+    public K next() {
+      return holder.entries[index++].getKey();
+    }
+
+    @Override
+    public void remove() {
+      throw new UnsupportedOperationException("remove");
+    }
+  }
+
+  private final class ArrayValueIterator<K, V> implements Iterator<V> {
+    int index;
+    private final ArrayHolder<K, V> holder;
+
+    private ArrayValueIterator(ArrayHolder<K, V> holder) {
+      this.holder = holder;
+      index = holder.startIndex;
+    }
+
+
+    @Override
+    public boolean hasNext() {
+      return index < holder.endIndex;
+    }
+
+    @Override
+    public V next() {
+      return holder.entries[index++].getValue();
+    }
+
+    @Override
+    public void remove() {
+      throw new UnsupportedOperationException("remove");
+    }
+  }
+
+  private final class ArrayEntryIterator<K, V> implements 
Iterator<Map.Entry<K, V>> {
+
+    int index;
+    private final ArrayHolder<K, V> holder;
+
+    private ArrayEntryIterator(ArrayHolder<K, V> holder) {
+      this.holder = holder;
+      this.index = holder.startIndex;
+    }
+
+    @Override
+    public boolean hasNext() {
+      return index < holder.endIndex;
+    }
+
+    @Override
+    public Entry<K, V> next() {
+      return holder.entries[index++];
+    }
+
+    @Override
+    public void remove() {
+      throw new UnsupportedOperationException("remove");
+    }
+  }
+
+  private final class ArrayEntrySet<K, V> implements Set<Map.Entry<K, V>> {
+    private final ArrayHolder<K, V> holder;
+
+    private ArrayEntrySet(ArrayHolder<K, V> holder) {
+      this.holder = holder;
+    }
+
+    @Override
+    public int size() {
+      return holder.getLength();
+    }
+
+    @Override
+    public boolean isEmpty() {
+      return holder.getLength() == 0;
+    }
+
+    @Override
+    public boolean contains(Object o) {
+      return false;
+    }
+
+    @Override
+    public Iterator<Entry<K, V>> iterator() {
+      return new ArrayEntryIterator<>(this.holder);
+    }
+
+    @Override
+    public Object[] toArray() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <T> T[] toArray(T[] a) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean add(Entry<K, V> kvEntry) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean remove(Object o) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean containsAll(Collection<?> c) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean addAll(Collection<? extends Entry<K, V>> c) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean retainAll(Collection<?> c) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean removeAll(Collection<?> c) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void clear() {
+      throw new UnsupportedOperationException();
+    }
+  }
+
+  private final static class ArrayHolder<K, V> {
+    private final COWEntry<K, V>[] entries;
+    private final int startIndex;
+    private final int endIndex;
+    private final Comparator<? super K> keyComparator;
+    private final Comparator<Map.Entry<K, V>> comparator;
+
+
+    int getLength() {
+      return endIndex - startIndex;
+    }
+
+
+    /**
+     * Binary search for a given key
+     * @param needle The key to look for in all of the entries
+     * @return Same return value as Arrays.binarySearch.
+     * Positive numbers mean the index.
+     * Otherwise (-1 * insertion point) - 1
+     */
+    int find(K needle) {
+      int begin = startIndex;
+      int end = endIndex - 1;
+
+      while (begin <= end) {
+        int mid = begin + ((end - begin) / 2);
+        K midKey = entries[ mid].key;
+        int compareRes = keyComparator.compare(midKey, needle);
+
+        // 0 means equals
+        // We found the key.
+        if (compareRes == 0) {
+          return mid;
+        } else if (compareRes < 0) {
+          // midKey is less than needle so we need
+          // to look at farther up
+          begin = mid + 1;
+        } else {
+          // midKey is greater than needle so we
+          // need to look down.
+          end = mid - 1;
+        }
+      }
+
+      return (-1 * begin) - 1;
+    }
+
+    ArrayHolder<K, V> replace(int index, COWEntry<K, V> newEntry) {
+      // TODO should this restart the array back at start index 0 ?
+      COWEntry<K, V>[] newEntries = entries.clone();
+      newEntries[index] = newEntry;
+      return new ArrayHolder<>(newEntries, startIndex, endIndex, 
keyComparator, comparator);
+    }
+
+    ArrayHolder<K, V> remove(int index) {
+      COWEntry<K,V>[] newEntries = new COWEntry[getLength() - 1];
+      System.arraycopy(this.entries, startIndex, newEntries, 0, index - 
startIndex);
+      System.arraycopy(this.entries, index + 1, newEntries, index, 
entries.length - index - 1);
+      return new ArrayHolder<>(newEntries, 0, newEntries.length, 
keyComparator, comparator);
+    }
+
+    ArrayHolder<K, V> insert(int index, COWEntry<K, V> newEntry) {
+      COWEntry<K,V>[] newEntries = new COWEntry[getLength() + 1];
+      System.arraycopy(this.entries, startIndex, newEntries, 0, index - 
startIndex);
+      newEntries[index] = newEntry;
+      System.arraycopy(this.entries, index, newEntries, index + 1, getLength() 
- index);
+      return new ArrayHolder<>(newEntries, 0, newEntries.length, 
keyComparator, comparator);
+    }
+
+    private ArrayHolder(
+        final Comparator<? super K> keyComparator,
+        final Comparator<Map.Entry<K, V>> comparator) {
+      this.endIndex = 0;
+      this.startIndex = 0;
+      this.entries = new COWEntry[] {};
+      this.keyComparator = keyComparator;
+      this.comparator = comparator;
+    }
+
+    private ArrayHolder(COWEntry[] entries,
+                        int startIndex, int endIndex,
+                        final Comparator<? super K> keyComparator,
+                        Comparator<Map.Entry<K, V>> comparator) {
+      this.entries = entries;
+      this.startIndex = startIndex;
+      this.endIndex = endIndex;
+      this.keyComparator = keyComparator;
+      this.comparator = comparator;
+    }
+  }
+
+  private final static class COWEntry<K, V> implements Map.Entry<K, V> {
+    K key = null;
+    V value = null;
+
+    COWEntry(K key, V value) {
+      this.key = key;
+      this.value = value;
+    }
+
+    @Override
+    public K getKey() {
+      return key;
+    }
+
+    @Override
+    public V getValue() {
+      return value;
+    }
+
+    @Override
+    public V setValue(V value) {
+      V oldValue = this.value;
+      this.value = value;
+      return oldValue;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/hbase/blob/4d5ac316/hbase-common/src/test/java/org/apache/hadoop/hbase/types/TestCopyOnWriteMaps.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..a3c892a
--- /dev/null
+++ 
b/hbase-common/src/test/java/org/apache/hadoop/hbase/types/TestCopyOnWriteMaps.java
@@ -0,0 +1,311 @@
+/**
+ * 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.hadoop.hbase.types;
+
+import org.apache.hadoop.hbase.testclassification.SmallTests;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentNavigableMap;
+import java.util.concurrent.ConcurrentSkipListMap;
+import java.util.concurrent.ThreadLocalRandom;
+
+import static org.junit.Assert.*;
+
+@Category(SmallTests.class)
+public class TestCopyOnWriteMaps {
+
+  private static final int MAX_RAND = 10 * 1000 * 1000;
+  private ConcurrentNavigableMap<Long, Long> m;
+  private ConcurrentSkipListMap<Long, Long> csm;
+
+  @Before
+  public void setUp() {
+    m = new CopyOnWriteArrayMap<>();
+    csm = new ConcurrentSkipListMap<>();
+
+    for (  long i = 0 ; i < 10000; i++ ) {
+      long o = ThreadLocalRandom.current().nextLong(MAX_RAND);
+      m.put(i, o);
+      csm.put(i,o);
+    }
+    long o = ThreadLocalRandom.current().nextLong(MAX_RAND);
+    m.put(0L, o);
+    csm.put(0L,o);
+  }
+
+  @Test
+  public void testSize() throws Exception {
+    assertEquals("Size should always be equal", m.size(), csm.size());
+  }
+
+  @Test
+  public void testIsEmpty() throws Exception {
+    m.clear();
+    assertTrue(m.isEmpty());
+    m.put(100L, 100L);
+    assertFalse(m.isEmpty());
+    m.remove(100L);
+    assertTrue(m.isEmpty());
+  }
+
+  @Test
+  public void testFindOnEmpty() throws Exception {
+    m.clear();
+    assertTrue(m.isEmpty());
+    assertNull(m.get(100L));
+    assertFalse(m.containsKey(100L));
+    assertEquals(0, m.tailMap(100L).entrySet().size());
+  }
+
+
+  @Test
+  public void testLowerKey() throws Exception {
+
+    assertEquals(csm.lowerKey(400L), m.lowerKey(400L));
+    assertEquals(csm.lowerKey(-1L), m.lowerKey(-1L));
+
+    for ( int i =0 ; i < 100; i ++) {
+      Long key = ThreadLocalRandom.current().nextLong();
+      assertEquals(csm.lowerKey(key), m.lowerKey(key));
+    }
+  }
+
+  @Test
+  public void testFloorEntry() throws Exception {
+    for ( int i =0 ; i < 100; i ++) {
+      Long key = ThreadLocalRandom.current().nextLong();
+      assertEquals(csm.floorEntry(key), m.floorEntry(key));
+    }
+  }
+
+  @Test
+  public void testFloorKey() throws Exception {
+    for ( int i =0 ; i < 100; i ++) {
+      Long key = ThreadLocalRandom.current().nextLong();
+      assertEquals(csm.floorKey(key), m.floorKey(key));
+    }
+  }
+
+  @Test
+  public void testCeilingKey() throws Exception {
+
+    assertEquals(csm.ceilingKey(4000L), m.ceilingKey(4000L));
+    assertEquals(csm.ceilingKey(400L), m.ceilingKey(400L));
+    assertEquals(csm.ceilingKey(-1L), m.ceilingKey(-1L));
+
+    for ( int i =0 ; i < 100; i ++) {
+      Long key = ThreadLocalRandom.current().nextLong();
+      assertEquals(csm.ceilingKey(key), m.ceilingKey(key));
+    }
+  }
+
+  @Test
+  public void testHigherKey() throws Exception {
+
+    assertEquals(csm.higherKey(4000L), m.higherKey(4000L));
+    assertEquals(csm.higherKey(400L), m.higherKey(400L));
+    assertEquals(csm.higherKey(-1L), m.higherKey(-1L));
+
+    for ( int i =0 ; i < 100; i ++) {
+      Long key = ThreadLocalRandom.current().nextLong();
+      assertEquals(csm.higherKey(key), m.higherKey(key));
+    }
+  }
+
+  @Test
+  public void testRemove() throws Exception {
+    for (Map.Entry<Long, Long> e:csm.entrySet()) {
+      assertEquals(csm.remove(e.getKey()), m.remove(e.getKey()));
+      assertEquals(null, m.remove(e.getKey()));
+    }
+  }
+
+  @Test
+  public void testReplace() throws Exception {
+    for (Map.Entry<Long, Long> e:csm.entrySet()) {
+      Long newValue = ThreadLocalRandom.current().nextLong();
+      assertEquals(csm.replace(e.getKey(), newValue), m.replace(e.getKey(), 
newValue));
+    }
+    assertEquals(null, m.replace(MAX_RAND + 100L, 
ThreadLocalRandom.current().nextLong()));
+  }
+
+  @Test
+  public void testReplace1() throws Exception {
+    for (Map.Entry<Long, Long> e: csm.entrySet()) {
+      Long newValue = ThreadLocalRandom.current().nextLong();
+      assertEquals(csm.replace(e.getKey(), e.getValue() + 1, newValue),
+          m.replace(e.getKey(), e.getValue() + 1, newValue));
+      assertEquals(csm.replace(e.getKey(), e.getValue(), newValue),
+          m.replace(e.getKey(), e.getValue(), newValue));
+      assertEquals(newValue, m.get(e.getKey()));
+      assertEquals(csm.get(e.getKey()), m.get(e.getKey()));
+    }
+    assertEquals(null, m.replace(MAX_RAND + 100L, 
ThreadLocalRandom.current().nextLong()));
+  }
+
+  @Test
+  public void testMultiAdd() throws InterruptedException {
+
+    Thread[] threads = new Thread[10];
+    for ( int i =0 ; i<threads.length; i++) {
+      threads[i] = new Thread(new Runnable() {
+        @Override
+        public void run() {
+          for ( int j = 0; j < 5000; j++) {
+            m.put(ThreadLocalRandom.current().nextLong(),
+                ThreadLocalRandom.current().nextLong());
+          }
+        }
+      });
+    }
+
+    for (Thread thread1 : threads) {
+      thread1.start();
+    }
+
+    for (Thread thread2 : threads) {
+      thread2.join();
+    }
+  }
+
+  @Test
+  public void testFirstKey() throws Exception {
+    assertEquals(csm.firstKey(), m.firstKey());
+  }
+
+  @Test
+  public void testLastKey() throws Exception {
+    assertEquals(csm.lastKey(), m.lastKey());
+  }
+
+  @Test
+  public void testFirstEntry() throws Exception {
+    assertEquals(csm.firstEntry().getKey(), m.firstEntry().getKey());
+    assertEquals(csm.firstEntry().getValue(), m.firstEntry().getValue());
+    assertEquals(csm.firstEntry(), m.firstEntry());
+  }
+
+  @Test
+  public void testLastEntry() throws Exception {
+    assertEquals(csm.lastEntry().getKey(), m.lastEntry().getKey());
+    assertEquals(csm.lastEntry().getValue(), m.lastEntry().getValue());
+    assertEquals(csm.lastEntry(), m.lastEntry());
+  }
+
+  @Test
+  public void testKeys() throws Exception {
+    for (Long key:csm.keySet()) {
+      //assertTrue(m.containsKey(key));
+      assertNotNull(m.get(key));
+      assertNotNull(m.remove(key));
+      assertNull(m.get(key));
+    }
+  }
+
+  @Test
+  public void testValues() throws Exception {
+    for (Long value:m.values()) {
+      assertTrue(csm.values().contains(value));
+      assertTrue(m.containsValue(value));
+    }
+  }
+
+  @Test
+  public void testTailMap() throws Exception {
+    Map<Long, Long> fromCsm = csm.tailMap(50L);
+    Map<Long, Long> fromM = m.tailMap(50L);
+    assertEquals(fromCsm, fromM);
+    for (Long value:m.keySet()) {
+      assertEquals(csm.tailMap(value), m.tailMap(value));
+    }
+
+    for (  long i = 0 ; i < 100; i++ ) {
+      long o = ThreadLocalRandom.current().nextLong(MAX_RAND);
+      assertEquals(csm.tailMap(o), m.tailMap(o));
+    }
+  }
+
+  @Test
+  public void testTailMapExclusive() throws Exception {
+    m.clear();
+    m.put(100L, 100L);
+    m.put(101L, 101L);
+    m.put(101L, 101L);
+    m.put(103L, 103L);
+    m.put(99L, 99L);
+    m.put(102L, 102L);
+
+    long n = 100L;
+    CopyOnWriteArrayMap<Long,Long> tm99 = (CopyOnWriteArrayMap<Long, Long>) 
m.tailMap(99L, false);
+    for (Map.Entry<Long,Long> e:tm99.entrySet()) {
+      assertEquals(new Long(n), e.getKey());
+      assertEquals(new Long(n), e.getValue());
+      n++;
+    }
+  }
+
+  @Test
+  public void testTailMapInclusive() throws Exception {
+    m.clear();
+    m.put(100L, 100L);
+    m.put(101L, 101L);
+    m.put(101L, 101L);
+    m.put(103L, 103L);
+    m.put(99L, 99L);
+    m.put(102L, 102L);
+
+    long n = 102;
+    CopyOnWriteArrayMap<Long,Long> tm102 = (CopyOnWriteArrayMap<Long, Long>) 
m.tailMap(102L, true);
+    for (Map.Entry<Long,Long> e:tm102.entrySet()) {
+      assertEquals(new Long(n), e.getKey());
+      assertEquals(new Long(n), e.getValue());
+      n++;
+    }
+    n = 99;
+    CopyOnWriteArrayMap<Long,Long> tm98 = (CopyOnWriteArrayMap<Long, Long>) 
m.tailMap(98L, true);
+    for (Map.Entry<Long,Long> e:tm98.entrySet()) {
+      assertEquals(new Long(n), e.getKey());
+      assertEquals(new Long(n), e.getValue());
+      n++;
+    }
+  }
+
+  @Test
+  public void testPut() throws Exception {
+    m.clear();
+    m.put(100L, 100L);
+    m.put(101L, 101L);
+    m.put(101L, 101L);
+    m.put(103L, 103L);
+    m.put(99L, 99L);
+    m.put(102L, 102L);
+    long n = 99;
+
+    for (Map.Entry<Long, Long> e:m.entrySet()) {
+      assertEquals(new Long(n), e.getKey());
+      assertEquals(new Long(n), e.getValue());
+      n++;
+    }
+    assertEquals(5, m.size());
+    assertEquals(false, m.isEmpty());
+  }
+}
\ No newline at end of file

Reply via email to