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

kturner pushed a commit to branch 2.1
in repository https://gitbox.apache.org/repos/asf/accumulo.git


The following commit(s) were added to refs/heads/2.1 by this push:
     new b478920a69 Clears unreferenced tables from TabletLocator (#6196)
b478920a69 is described below

commit b478920a697fa962cbeb28611936321378fbdad6
Author: Keith Turner <[email protected]>
AuthorDate: Mon Mar 9 18:08:22 2026 -0400

    Clears unreferenced tables from TabletLocator (#6196)
    
    
    fixes #6164
---
 .../accumulo/core/clientImpl/TabletLocator.java    | 70 +++++++++++++++++--
 .../java/org/apache/accumulo/test/LocatorIT.java   | 80 ++++++++++++++++++++++
 2 files changed, 145 insertions(+), 5 deletions(-)

diff --git 
a/core/src/main/java/org/apache/accumulo/core/clientImpl/TabletLocator.java 
b/core/src/main/java/org/apache/accumulo/core/clientImpl/TabletLocator.java
index 353bfd6da0..6375b9cf84 100644
--- a/core/src/main/java/org/apache/accumulo/core/clientImpl/TabletLocator.java
+++ b/core/src/main/java/org/apache/accumulo/core/clientImpl/TabletLocator.java
@@ -20,6 +20,7 @@ package org.apache.accumulo.core.clientImpl;
 
 import static com.google.common.base.Preconditions.checkArgument;
 
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -41,8 +42,10 @@ import org.apache.accumulo.core.metadata.RootTable;
 import org.apache.accumulo.core.singletons.SingletonManager;
 import org.apache.accumulo.core.singletons.SingletonService;
 import org.apache.accumulo.core.util.Interner;
+import org.apache.accumulo.core.util.Timer;
 import org.apache.hadoop.io.Text;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
 
 public abstract class TabletLocator {
@@ -111,7 +114,8 @@ public abstract class TabletLocator {
   }
 
   private static final HashMap<LocatorKey,TabletLocator> locators = new 
HashMap<>();
-  private static final HashMap<TableId,OfflineTabletLocatorImpl> 
offlineLocators = new HashMap<>();
+  private static final HashMap<LocatorKey,OfflineTabletLocatorImpl> 
offlineLocators =
+      new HashMap<>();
   private static boolean enabled = true;
 
   public static synchronized void clearLocators() {
@@ -138,15 +142,17 @@ public abstract class TabletLocator {
   public static synchronized TabletLocator getLocator(ClientContext context, 
TableId tableId) {
     Preconditions.checkState(enabled, "The Accumulo singleton that that tracks 
tablet locations is "
         + "disabled. This is likely caused by all AccumuloClients being closed 
or garbage collected");
+
+    clearUnusedTables(context);
+
     TableState state = context.getTableState(tableId);
+    LocatorKey key = new LocatorKey(context.getInstanceID(), tableId);
     if (state == TableState.OFFLINE) {
-      LocatorKey key = new LocatorKey(context.getInstanceID(), tableId);
       locators.remove(key);
-      return offlineLocators.computeIfAbsent(tableId,
+      return offlineLocators.computeIfAbsent(key,
           f -> new OfflineTabletLocatorImpl(context, tableId));
     } else {
-      offlineLocators.remove(tableId);
-      LocatorKey key = new LocatorKey(context.getInstanceID(), tableId);
+      offlineLocators.remove(key);
       TabletLocator tl = locators.get(key);
       if (tl == null) {
         MetadataLocationObtainer mlo = new MetadataLocationObtainer();
@@ -167,6 +173,60 @@ public abstract class TabletLocator {
 
   }
 
+  /**
+   * Checks if a table id is present in the cache w/o creating it.
+   */
+  @VisibleForTesting
+  public static synchronized boolean isPresent(ClientContext context, TableId 
tableId) {
+    LocatorKey key = new LocatorKey(context.getInstanceID(), tableId);
+    return locators.containsKey(key) || offlineLocators.containsKey(key);
+  }
+
+  private static Duration clearFrequency = Duration.ofMinutes(10);
+
+  /**
+   * Sets how often checks for unused tables are done
+   */
+  @VisibleForTesting
+  public static synchronized void setClearFrequency(Duration frequency) {
+    Preconditions.checkArgument(frequency != null && !frequency.isNegative() 
&& !frequency.isZero(),
+        "frequency:%s", frequency);
+    clearFrequency = frequency;
+  }
+
+  private static final Timer lastClearTimer = Timer.startNew();
+
+  /**
+   * Finds and clears any tables ids in the cache that are no longer in used.
+   */
+  private static synchronized void clearUnusedTables(ClientContext context) {
+    if (lastClearTimer.hasElapsed(clearFrequency)) {
+      locators.entrySet().removeIf(entry -> {
+        LocatorKey lkey = entry.getKey();
+        TabletLocator locator = entry.getValue();
+        if (lkey.instanceId.equals(context.getInstanceID())
+            && context.getTableState(lkey.tableId) != TableState.ONLINE) {
+          locator.isValid = false;
+          locator.invalidateCache();
+          return true;
+        }
+        return false;
+      });
+      offlineLocators.entrySet().removeIf(entry -> {
+        LocatorKey lkey = entry.getKey();
+        TabletLocator locator = entry.getValue();
+        if (lkey.instanceId.equals(context.getInstanceID())
+            && context.getTableState(lkey.tableId) != TableState.OFFLINE) {
+          locator.isValid = false;
+          locator.invalidateCache();
+          return true;
+        }
+        return false;
+      });
+      lastClearTimer.restart();
+    }
+  }
+
   static {
     SingletonManager.register(new SingletonService() {
 
diff --git a/test/src/main/java/org/apache/accumulo/test/LocatorIT.java 
b/test/src/main/java/org/apache/accumulo/test/LocatorIT.java
index 289a6f8557..c51af1e0b5 100644
--- a/test/src/main/java/org/apache/accumulo/test/LocatorIT.java
+++ b/test/src/main/java/org/apache/accumulo/test/LocatorIT.java
@@ -19,6 +19,7 @@
 package org.apache.accumulo.test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -38,13 +39,18 @@ import org.apache.accumulo.core.client.AccumuloClient;
 import org.apache.accumulo.core.client.TableNotFoundException;
 import org.apache.accumulo.core.client.TableOfflineException;
 import org.apache.accumulo.core.client.admin.Locations;
+import org.apache.accumulo.core.client.admin.NewTableConfiguration;
 import org.apache.accumulo.core.client.admin.TableOperations;
+import org.apache.accumulo.core.clientImpl.ClientContext;
+import org.apache.accumulo.core.clientImpl.TabletLocator;
 import org.apache.accumulo.core.data.Range;
 import org.apache.accumulo.core.data.TableId;
 import org.apache.accumulo.core.data.TabletId;
 import org.apache.accumulo.core.dataImpl.KeyExtent;
 import org.apache.accumulo.core.dataImpl.TabletIdImpl;
+import org.apache.accumulo.core.manager.state.tables.TableState;
 import org.apache.accumulo.harness.AccumuloClusterHarness;
+import org.apache.accumulo.test.util.Wait;
 import org.apache.hadoop.io.Text;
 import org.junit.jupiter.api.Test;
 
@@ -132,4 +138,78 @@ public class LocatorIT extends AccumuloClusterHarness {
       assertThrows(TableNotFoundException.class, () -> 
tableOps.locate(tableName, ranges));
     }
   }
+
+  @Test
+  public void testClearingUnused() throws Exception {
+    try (AccumuloClient client = 
Accumulo.newClient().from(getClientProps()).build()) {
+      String[] tables = getUniqueNames(4);
+      String table1 = tables[0];
+      String table2 = tables[1];
+      String table3 = tables[2];
+      String table4 = tables[3];
+
+      TableOperations tableOps = client.tableOperations();
+      tableOps.create(table1);
+      tableOps.create(table2);
+      tableOps.create(table3, new NewTableConfiguration().createOffline());
+      tableOps.create(table4, new NewTableConfiguration().createOffline());
+
+      TabletLocator.setClearFrequency(Duration.ofMillis(100));
+
+      ClientContext ctx = (ClientContext) client;
+      TableId tableId1 = ctx.getTableId(table1);
+      TableId tableId2 = ctx.getTableId(table2);
+      TableId tableId3 = ctx.getTableId(table3);
+      TableId tableId4 = ctx.getTableId(table4);
+
+      for (var tableId : List.of(tableId1, tableId2, tableId3, tableId4)) {
+        assertFalse(TabletLocator.isPresent(ctx, tableId));
+        assertNotNull(TabletLocator.getLocator(ctx, tableId));
+        assertTrue(TabletLocator.isPresent(ctx, tableId));
+      }
+
+      // Put table2 and table3 into a different state than what is in the cache
+      assertEquals(TableState.ONLINE, ctx.getTableState(tableId2));
+      assertEquals(TableState.OFFLINE, ctx.getTableState(tableId3));
+      tableOps.offline(table2, true);
+      tableOps.online(table3, true);
+      assertEquals(TableState.OFFLINE, ctx.getTableState(tableId2));
+      assertEquals(TableState.ONLINE, ctx.getTableState(tableId3));
+
+      Wait.waitFor(() -> {
+        // Accessing table1 in the cache should cause table2 and table3 to 
eventually be cleared
+        // because their table state does not match what was cached
+        assertNotNull(TabletLocator.getLocator(ctx, tableId1));
+        return !TabletLocator.isPresent(ctx, tableId2) && 
!TabletLocator.isPresent(ctx, tableId3);
+      });
+
+      assertTrue(TabletLocator.isPresent(ctx, tableId1));
+      assertTrue(TabletLocator.isPresent(ctx, tableId4));
+
+      // bring table2 and table3 back into the cache
+      for (var tableId : List.of(tableId2, tableId3)) {
+        assertFalse(TabletLocator.isPresent(ctx, tableId));
+        assertNotNull(TabletLocator.getLocator(ctx, tableId));
+        assertTrue(TabletLocator.isPresent(ctx, tableId));
+      }
+
+      tableOps.delete(table2);
+      tableOps.delete(table3);
+
+      Wait.waitFor(() -> {
+        // Accessing table4 in the cache should cause table2 and table3 to 
eventually be cleared
+        // because they no longer exist. This also test that online and 
offline tables a properly
+        // cleared from the cache.
+        assertNotNull(TabletLocator.getLocator(ctx, tableId4));
+        return !TabletLocator.isPresent(ctx, tableId2) && 
!TabletLocator.isPresent(ctx, tableId3);
+      });
+
+      // table1 and table4 should be left in the cache, check that online or 
offline tables are not
+      // removed unnecessarily.
+      assertTrue(TabletLocator.isPresent(ctx, tableId1));
+      assertTrue(TabletLocator.isPresent(ctx, tableId4));
+      assertEquals(TableState.ONLINE, ctx.getTableState(tableId1));
+      assertEquals(TableState.OFFLINE, ctx.getTableState(tableId4));
+    }
+  }
 }

Reply via email to