This is an automated email from the ASF dual-hosted git repository.

cconnell pushed a commit to branch branch-2
in repository https://gitbox.apache.org/repos/asf/hbase.git


The following commit(s) were added to refs/heads/branch-2 by this push:
     new dcd9bed80ce HBASE-29479: QuotaCache should always return accurate 
information (#7188)
dcd9bed80ce is described below

commit dcd9bed80cee49e5557a4792093c310a8ccd92f7
Author: Charles Connell <[email protected]>
AuthorDate: Wed Sep 3 13:52:45 2025 -0400

    HBASE-29479: QuotaCache should always return accurate information (#7188)
    
    Signed-off by: Ray Mattingly <[email protected]>
---
 .../org/apache/hadoop/hbase/quotas/QuotaCache.java | 209 +++++++++++----------
 .../hbase/quotas/TestDefaultAtomicQuota.java       |   9 -
 .../apache/hadoop/hbase/quotas/TestQuotaCache.java | 118 +++++++++++-
 3 files changed, 227 insertions(+), 109 deletions(-)

diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/QuotaCache.java 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/QuotaCache.java
index d77f6219ae5..2ec9d049f7d 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/QuotaCache.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/QuotaCache.java
@@ -17,8 +17,6 @@
  */
 package org.apache.hadoop.hbase.quotas;
 
-import static org.apache.hadoop.hbase.util.ConcurrentMapUtils.computeIfAbsent;
-
 import java.io.IOException;
 import java.time.Duration;
 import java.util.ArrayList;
@@ -30,6 +28,7 @@ import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.ClusterMetrics;
 import org.apache.hadoop.hbase.ClusterMetrics.Option;
@@ -56,10 +55,7 @@ import 
org.apache.hbase.thirdparty.com.google.common.cache.LoadingCache;
 
 /**
  * Cache that keeps track of the quota settings for the users and tables that 
are interacting with
- * it. To avoid blocking the operations if the requested quota is not in cache 
an "empty quota" will
- * be returned and the request to fetch the quota information will be enqueued 
for the next refresh.
- * TODO: At the moment the Cache has a Chore that will be triggered every 5min 
or on cache-miss
- * events. Later the Quotas will be pushed using the notification system.
+ * it.
  */
 @InterfaceAudience.Private
 @InterfaceStability.Evolving
@@ -100,6 +96,62 @@ public class QuotaCache implements Stoppable {
   private QuotaRefresherChore refreshChore;
   private boolean stopped = true;
 
+  private final Fetcher<String, UserQuotaState> userQuotaStateFetcher =
+    new Fetcher<String, UserQuotaState>() {
+      @Override
+      public Get makeGet(final String user) {
+        final Set<String> namespaces = 
QuotaCache.this.namespaceQuotaCache.keySet();
+        final Set<TableName> tables = QuotaCache.this.tableQuotaCache.keySet();
+        return QuotaUtil.makeGetForUserQuotas(user, tables, namespaces);
+      }
+
+      @Override
+      public Map<String, UserQuotaState> fetchEntries(final List<Get> gets) 
throws IOException {
+        return QuotaUtil.fetchUserQuotas(rsServices.getConnection(), gets, 
tableMachineQuotaFactors,
+          machineQuotaFactor);
+      }
+    };
+
+  private final Fetcher<String, QuotaState> regionServerQuotaStateFetcher =
+    new Fetcher<String, QuotaState>() {
+      @Override
+      public Get makeGet(final String regionServer) {
+        return QuotaUtil.makeGetForRegionServerQuotas(regionServer);
+      }
+
+      @Override
+      public Map<String, QuotaState> fetchEntries(final List<Get> gets) throws 
IOException {
+        return QuotaUtil.fetchRegionServerQuotas(rsServices.getConnection(), 
gets);
+      }
+    };
+
+  private final Fetcher<TableName, QuotaState> tableQuotaStateFetcher =
+    new Fetcher<TableName, QuotaState>() {
+      @Override
+      public Get makeGet(final TableName table) {
+        return QuotaUtil.makeGetForTableQuotas(table);
+      }
+
+      @Override
+      public Map<TableName, QuotaState> fetchEntries(final List<Get> gets) 
throws IOException {
+        return QuotaUtil.fetchTableQuotas(rsServices.getConnection(), gets,
+          tableMachineQuotaFactors);
+      }
+    };
+
+  private final Fetcher<String, QuotaState> namespaceQuotaStateFetcher =
+    new Fetcher<String, QuotaState>() {
+      @Override
+      public Get makeGet(final String namespace) {
+        return QuotaUtil.makeGetForNamespaceQuotas(namespace);
+      }
+
+      @Override
+      public Map<String, QuotaState> fetchEntries(final List<Get> gets) throws 
IOException {
+        return QuotaUtil.fetchNamespaceQuotas(rsServices.getConnection(), 
gets, machineQuotaFactor);
+      }
+    };
+
   public QuotaCache(final RegionServerServices rsServices) {
     this.rsServices = rsServices;
     this.userOverrideRequestAttributeKey =
@@ -153,8 +205,13 @@ public class QuotaCache implements Stoppable {
    * @return the quota info associated to specified user
    */
   public UserQuotaState getUserQuotaState(final UserGroupInformation ugi) {
-    return computeIfAbsent(userQuotaCache, getQuotaUserName(ugi),
-      () -> 
QuotaUtil.buildDefaultUserQuotaState(rsServices.getConfiguration(), 0L));
+    String user = getQuotaUserName(ugi);
+    if (!userQuotaCache.containsKey(user)) {
+      userQuotaCache.put(user,
+        QuotaUtil.buildDefaultUserQuotaState(rsServices.getConfiguration(), 
0L));
+      fetch("user", userQuotaCache, userQuotaStateFetcher);
+    }
+    return userQuotaCache.get(user);
   }
 
   /**
@@ -163,7 +220,11 @@ public class QuotaCache implements Stoppable {
    * @return the limiter associated to the specified table
    */
   public QuotaLimiter getTableLimiter(final TableName table) {
-    return getQuotaState(this.tableQuotaCache, table).getGlobalLimiter();
+    if (!tableQuotaCache.containsKey(table)) {
+      tableQuotaCache.put(table, new QuotaState());
+      fetch("table", tableQuotaCache, tableQuotaStateFetcher);
+    }
+    return tableQuotaCache.get(table).getGlobalLimiter();
   }
 
   /**
@@ -172,7 +233,11 @@ public class QuotaCache implements Stoppable {
    * @return the limiter associated to the specified namespace
    */
   public QuotaLimiter getNamespaceLimiter(final String namespace) {
-    return getQuotaState(this.namespaceQuotaCache, 
namespace).getGlobalLimiter();
+    if (!namespaceQuotaCache.containsKey(namespace)) {
+      namespaceQuotaCache.put(namespace, new QuotaState());
+      fetch("namespace", namespaceQuotaCache, namespaceQuotaStateFetcher);
+    }
+    return namespaceQuotaCache.get(namespace).getGlobalLimiter();
   }
 
   /**
@@ -181,13 +246,41 @@ public class QuotaCache implements Stoppable {
    * @return the limiter associated to the specified region server
    */
   public QuotaLimiter getRegionServerQuotaLimiter(final String regionServer) {
-    return getQuotaState(this.regionServerQuotaCache, 
regionServer).getGlobalLimiter();
+    if (!regionServerQuotaCache.containsKey(regionServer)) {
+      regionServerQuotaCache.put(regionServer, new QuotaState());
+      fetch("regionServer", regionServerQuotaCache, 
regionServerQuotaStateFetcher);
+    }
+    return regionServerQuotaCache.get(regionServer).getGlobalLimiter();
   }
 
   protected boolean isExceedThrottleQuotaEnabled() {
     return exceedThrottleQuotaEnabled;
   }
 
+  private <K, V extends QuotaState> void fetch(final String type, final Map<K, 
V> quotasMap,
+    final Fetcher<K, V> fetcher) {
+    // Find the quota entries to update
+    List<Get> gets = 
quotasMap.keySet().stream().map(fetcher::makeGet).collect(Collectors.toList());
+
+    // fetch and update the quota entries
+    if (!gets.isEmpty()) {
+      try {
+        for (Map.Entry<K, V> entry : fetcher.fetchEntries(gets).entrySet()) {
+          V quotaInfo = quotasMap.putIfAbsent(entry.getKey(), 
entry.getValue());
+          if (quotaInfo != null) {
+            quotaInfo.update(entry.getValue());
+          }
+
+          if (LOG.isTraceEnabled()) {
+            LOG.trace("Loading {} key={} quotas={}", type, entry.getKey(), 
quotaInfo);
+          }
+        }
+      } catch (IOException e) {
+        LOG.warn("Unable to read {} from quota table", type, e);
+      }
+    }
+  }
+
   /**
    * Applies a request attribute user override if available, otherwise returns 
the UGI's short
    * username
@@ -210,14 +303,6 @@ public class QuotaCache implements Stoppable {
     return Bytes.toString(override);
   }
 
-  /**
-   * Returns the QuotaState requested. If the quota info is not in cache an 
empty one will be
-   * returned and the quota request will be enqueued for the next cache 
refresh.
-   */
-  private <K> QuotaState getQuotaState(final ConcurrentMap<K, QuotaState> 
quotasMap, final K key) {
-    return computeIfAbsent(quotasMap, key, QuotaState::new);
-  }
-
   void triggerCacheRefresh() {
     refreshChore.triggerNow();
   }
@@ -226,10 +311,6 @@ public class QuotaCache implements Stoppable {
     refreshChore.chore();
   }
 
-  long getLastUpdate() {
-    return refreshChore.lastUpdate;
-  }
-
   Map<String, QuotaState> getNamespaceQuotaCache() {
     return namespaceQuotaCache;
   }
@@ -248,8 +329,6 @@ public class QuotaCache implements Stoppable {
 
   // TODO: Remove this once we have the notification bus
   private class QuotaRefresherChore extends ScheduledChore {
-    private long lastUpdate = 0;
-
     // Querying cluster metrics so often, per-RegionServer, limits horizontal 
scalability.
     // So we cache the results to reduce that load.
     private final RefreshableExpiringValueCache<ClusterMetrics> 
tableRegionStatesClusterMetrics;
@@ -307,74 +386,12 @@ public class QuotaCache implements Stoppable {
         .computeIfAbsent(QuotaTableUtil.QUOTA_REGION_SERVER_ROW_KEY, key -> 
new QuotaState());
 
       updateQuotaFactors();
-      fetchNamespaceQuotaState();
-      fetchTableQuotaState();
-      fetchUserQuotaState();
-      fetchRegionServerQuotaState();
+      fetchAndEvict("namespace", QuotaCache.this.namespaceQuotaCache, 
namespaceQuotaStateFetcher);
+      fetchAndEvict("table", QuotaCache.this.tableQuotaCache, 
tableQuotaStateFetcher);
+      fetchAndEvict("user", QuotaCache.this.userQuotaCache, 
userQuotaStateFetcher);
+      fetchAndEvict("regionServer", QuotaCache.this.regionServerQuotaCache,
+        regionServerQuotaStateFetcher);
       fetchExceedThrottleQuota();
-      lastUpdate = EnvironmentEdgeManager.currentTime();
-    }
-
-    private void fetchNamespaceQuotaState() {
-      fetch("namespace", QuotaCache.this.namespaceQuotaCache, new 
Fetcher<String, QuotaState>() {
-        @Override
-        public Get makeGet(final Map.Entry<String, QuotaState> entry) {
-          return QuotaUtil.makeGetForNamespaceQuotas(entry.getKey());
-        }
-
-        @Override
-        public Map<String, QuotaState> fetchEntries(final List<Get> gets) 
throws IOException {
-          return QuotaUtil.fetchNamespaceQuotas(rsServices.getConnection(), 
gets,
-            machineQuotaFactor);
-        }
-      });
-    }
-
-    private void fetchTableQuotaState() {
-      fetch("table", QuotaCache.this.tableQuotaCache, new Fetcher<TableName, 
QuotaState>() {
-        @Override
-        public Get makeGet(final Map.Entry<TableName, QuotaState> entry) {
-          return QuotaUtil.makeGetForTableQuotas(entry.getKey());
-        }
-
-        @Override
-        public Map<TableName, QuotaState> fetchEntries(final List<Get> gets) 
throws IOException {
-          return QuotaUtil.fetchTableQuotas(rsServices.getConnection(), gets,
-            tableMachineQuotaFactors);
-        }
-      });
-    }
-
-    private void fetchUserQuotaState() {
-      final Set<String> namespaces = 
QuotaCache.this.namespaceQuotaCache.keySet();
-      final Set<TableName> tables = QuotaCache.this.tableQuotaCache.keySet();
-      fetch("user", QuotaCache.this.userQuotaCache, new Fetcher<String, 
UserQuotaState>() {
-        @Override
-        public Get makeGet(final Map.Entry<String, UserQuotaState> entry) {
-          return QuotaUtil.makeGetForUserQuotas(entry.getKey(), tables, 
namespaces);
-        }
-
-        @Override
-        public Map<String, UserQuotaState> fetchEntries(final List<Get> gets) 
throws IOException {
-          return QuotaUtil.fetchUserQuotas(rsServices.getConnection(), gets,
-            tableMachineQuotaFactors, machineQuotaFactor);
-        }
-      });
-    }
-
-    private void fetchRegionServerQuotaState() {
-      fetch("regionServer", QuotaCache.this.regionServerQuotaCache,
-        new Fetcher<String, QuotaState>() {
-          @Override
-          public Get makeGet(final Map.Entry<String, QuotaState> entry) {
-            return QuotaUtil.makeGetForRegionServerQuotas(entry.getKey());
-          }
-
-          @Override
-          public Map<String, QuotaState> fetchEntries(final List<Get> gets) 
throws IOException {
-            return 
QuotaUtil.fetchRegionServerQuotas(rsServices.getConnection(), gets);
-          }
-        });
     }
 
     private void fetchExceedThrottleQuota() {
@@ -386,7 +403,7 @@ public class QuotaCache implements Stoppable {
       }
     }
 
-    private <K, V extends QuotaState> void fetch(final String type,
+    private <K, V extends QuotaState> void fetchAndEvict(final String type,
       final ConcurrentMap<K, V> quotasMap, final Fetcher<K, V> fetcher) {
       long now = EnvironmentEdgeManager.currentTime();
       long evictPeriod = getPeriod() * EVICT_PERIOD_FACTOR;
@@ -398,7 +415,7 @@ public class QuotaCache implements Stoppable {
         if (lastQuery > 0 && (now - lastQuery) >= evictPeriod) {
           toRemove.add(entry.getKey());
         } else {
-          gets.add(fetcher.makeGet(entry));
+          gets.add(fetcher.makeGet(entry.getKey()));
         }
       }
 
@@ -543,8 +560,8 @@ public class QuotaCache implements Stoppable {
     T get() throws Exception;
   }
 
-  static interface Fetcher<Key, Value> {
-    Get makeGet(Map.Entry<Key, Value> entry);
+  interface Fetcher<Key, Value> {
+    Get makeGet(Key key);
 
     Map<Key, Value> fetchEntries(List<Get> gets) throws IOException;
   }
diff --git 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestDefaultAtomicQuota.java
 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestDefaultAtomicQuota.java
index 966bce6bcdb..31840cb8d2f 100644
--- 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestDefaultAtomicQuota.java
+++ 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestDefaultAtomicQuota.java
@@ -81,10 +81,6 @@ public class TestDefaultAtomicQuota {
 
   @Test
   public void testDefaultAtomicReadLimits() throws Exception {
-    // No write throttling
-    configureLenientThrottle(ThrottleType.ATOMIC_WRITE_SIZE);
-    refreshQuotas();
-
     // Should have a strict throttle by default
     TEST_UTIL.waitFor(60_000, () -> runIncTest(100) < 100);
 
@@ -102,11 +98,6 @@ public class TestDefaultAtomicQuota {
 
   @Test
   public void testDefaultAtomicWriteLimits() throws Exception {
-    // No read throttling
-    configureLenientThrottle(ThrottleType.ATOMIC_REQUEST_NUMBER);
-    configureLenientThrottle(ThrottleType.ATOMIC_READ_SIZE);
-    refreshQuotas();
-
     // Should have a strict throttle by default
     TEST_UTIL.waitFor(60_000, () -> runIncTest(100) < 100);
 
diff --git 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestQuotaCache.java 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestQuotaCache.java
index 09e15236912..f4f876f104c 100644
--- 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestQuotaCache.java
+++ 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestQuotaCache.java
@@ -20,13 +20,16 @@ package org.apache.hadoop.hbase.quotas;
 import static 
org.apache.hadoop.hbase.quotas.ThrottleQuotaTestUtil.waitMinuteQuota;
 import static org.junit.Assert.assertEquals;
 
+import java.util.concurrent.TimeUnit;
 import org.apache.hadoop.hbase.HBaseClassTestRule;
 import org.apache.hadoop.hbase.HBaseTestingUtility;
+import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.client.Admin;
 import org.apache.hadoop.hbase.testclassification.MediumTests;
 import org.apache.hadoop.hbase.testclassification.RegionServerTests;
 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
 import org.apache.hadoop.security.UserGroupInformation;
-import org.junit.After;
+import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.ClassRule;
 import org.junit.Test;
@@ -42,8 +45,8 @@ public class TestQuotaCache {
   private static final HBaseTestingUtility TEST_UTIL = new 
HBaseTestingUtility();
   private static final int REFRESH_TIME_MS = 1000;
 
-  @After
-  public void tearDown() throws Exception {
+  @AfterClass
+  public static void tearDown() throws Exception {
     ThrottleQuotaTestUtil.clearQuotaCache(TEST_UTIL);
     EnvironmentEdgeManager.reset();
     TEST_UTIL.shutdownMiniCluster();
@@ -68,7 +71,6 @@ public class TestQuotaCache {
     UserGroupInformation ugi = UserGroupInformation.getCurrentUser();
 
     UserQuotaState userQuotaState = quotaCache.getUserQuotaState(ugi);
-    assertEquals(userQuotaState.getLastUpdate(), 0);
 
     QuotaCache.TEST_BLOCK_REFRESH = false;
     // new user should have refreshed immediately
@@ -86,4 +88,112 @@ public class TestQuotaCache {
     // should refresh after time has passed
     TEST_UTIL.waitFor(5_000, () -> lastUpdate != 
userQuotaState.getLastUpdate());
   }
+
+  @Test
+  public void testUserQuotaLookup() throws Exception {
+    QuotaCache quotaCache =
+      ThrottleQuotaTestUtil.getQuotaCaches(TEST_UTIL).stream().findAny().get();
+    final Admin admin = TEST_UTIL.getAdmin();
+    admin.setQuota(QuotaSettingsFactory.throttleUser("my_user", 
ThrottleType.READ_NUMBER, 3737,
+      TimeUnit.MINUTES));
+
+    // Setting a quota and then looking it up from the cache should work, even 
if the cache has not
+    // refreshed
+    UserGroupInformation ugi = 
UserGroupInformation.createRemoteUser("my_user");
+    QuotaLimiter quotaLimiter = quotaCache.getUserLimiter(ugi, 
TableName.valueOf("my_table"));
+    assertEquals(3737, quotaLimiter.getReadNumLimit());
+
+    // if no specific user quota, fall back to default
+    ugi = UserGroupInformation.createRemoteUser("my_user2");
+    quotaLimiter = quotaCache.getUserLimiter(ugi, 
TableName.valueOf("my_table"));
+    assertEquals(1000, quotaLimiter.getReadNumLimit());
+
+    // still works after refresh
+    quotaCache.forceSynchronousCacheRefresh();
+    ugi = UserGroupInformation.createRemoteUser("my_user");
+    quotaLimiter = quotaCache.getUserLimiter(ugi, 
TableName.valueOf("my_table"));
+    assertEquals(3737, quotaLimiter.getReadNumLimit());
+
+    ugi = UserGroupInformation.createRemoteUser("my_user2");
+    quotaLimiter = quotaCache.getUserLimiter(ugi, 
TableName.valueOf("my_table"));
+    assertEquals(1000, quotaLimiter.getReadNumLimit());
+  }
+
+  @Test
+  public void testTableQuotaLookup() throws Exception {
+    QuotaCache quotaCache =
+      ThrottleQuotaTestUtil.getQuotaCaches(TEST_UTIL).stream().findAny().get();
+    final Admin admin = TEST_UTIL.getAdmin();
+    
admin.setQuota(QuotaSettingsFactory.throttleTable(TableName.valueOf("my_table"),
+      ThrottleType.READ_NUMBER, 3737, TimeUnit.MINUTES));
+
+    // Setting a quota and then looking it up from the cache should work, even 
if the cache has not
+    // refreshed
+    QuotaLimiter quotaLimiter = 
quotaCache.getTableLimiter(TableName.valueOf("my_table"));
+    assertEquals(3737, quotaLimiter.getReadNumLimit());
+
+    // if no specific table quota, fall back to default
+    quotaLimiter = quotaCache.getTableLimiter(TableName.valueOf("my_table2"));
+    assertEquals(Long.MAX_VALUE, quotaLimiter.getReadNumLimit());
+
+    // still works after refresh
+    quotaCache.forceSynchronousCacheRefresh();
+    quotaLimiter = quotaCache.getTableLimiter(TableName.valueOf("my_table"));
+    assertEquals(3737, quotaLimiter.getReadNumLimit());
+
+    quotaLimiter = quotaCache.getTableLimiter(TableName.valueOf("my_table2"));
+    assertEquals(Long.MAX_VALUE, quotaLimiter.getReadNumLimit());
+  }
+
+  @Test
+  public void testNamespaceQuotaLookup() throws Exception {
+    QuotaCache quotaCache =
+      ThrottleQuotaTestUtil.getQuotaCaches(TEST_UTIL).stream().findAny().get();
+    final Admin admin = TEST_UTIL.getAdmin();
+    admin.setQuota(QuotaSettingsFactory.throttleNamespace("my_namespace", 
ThrottleType.READ_NUMBER,
+      3737, TimeUnit.MINUTES));
+
+    // Setting a quota and then looking it up from the cache should work, even 
if the cache has not
+    // refreshed
+    QuotaLimiter quotaLimiter = quotaCache.getNamespaceLimiter("my_namespace");
+    assertEquals(3737, quotaLimiter.getReadNumLimit());
+
+    // if no specific namespace quota, fall back to default
+    quotaLimiter = quotaCache.getNamespaceLimiter("my_namespace2");
+    assertEquals(Long.MAX_VALUE, quotaLimiter.getReadNumLimit());
+
+    // still works after refresh
+    quotaCache.forceSynchronousCacheRefresh();
+    quotaLimiter = quotaCache.getNamespaceLimiter("my_namespace");
+    assertEquals(3737, quotaLimiter.getReadNumLimit());
+
+    quotaLimiter = quotaCache.getNamespaceLimiter("my_namespace2");
+    assertEquals(Long.MAX_VALUE, quotaLimiter.getReadNumLimit());
+  }
+
+  @Test
+  public void testRegionServerQuotaLookup() throws Exception {
+    QuotaCache quotaCache =
+      ThrottleQuotaTestUtil.getQuotaCaches(TEST_UTIL).stream().findAny().get();
+    final Admin admin = TEST_UTIL.getAdmin();
+    
admin.setQuota(QuotaSettingsFactory.throttleRegionServer("my_region_server",
+      ThrottleType.READ_NUMBER, 3737, TimeUnit.MINUTES));
+
+    // Setting a quota and then looking it up from the cache should work, even 
if the cache has not
+    // refreshed
+    QuotaLimiter quotaLimiter = 
quotaCache.getRegionServerQuotaLimiter("my_region_server");
+    assertEquals(3737, quotaLimiter.getReadNumLimit());
+
+    // if no specific server quota, fall back to default
+    quotaLimiter = quotaCache.getRegionServerQuotaLimiter("my_region_server2");
+    assertEquals(Long.MAX_VALUE, quotaLimiter.getReadNumLimit());
+
+    // still works after refresh
+    quotaCache.forceSynchronousCacheRefresh();
+    quotaLimiter = quotaCache.getRegionServerQuotaLimiter("my_region_server");
+    assertEquals(3737, quotaLimiter.getReadNumLimit());
+
+    quotaLimiter = quotaCache.getRegionServerQuotaLimiter("my_region_server2");
+    assertEquals(Long.MAX_VALUE, quotaLimiter.getReadNumLimit());
+  }
 }

Reply via email to