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

yuqi1129 pushed a commit to branch branch-1.3
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/branch-1.3 by this push:
     new c33e7d40c9 [Cherry-pick to branch-1.3] [#12504] fix(core): Fix flaky 
TestCatalogManager.testCatalogCacheRemoveListener (#12514) (#12643)
c33e7d40c9 is described below

commit c33e7d40c9b55885af8cda4a984a4d6b1c89cd41
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Thu Aug 27 17:41:03 2026 +0800

    [Cherry-pick to branch-1.3] [#12504] fix(core): Fix flaky 
TestCatalogManager.testCatalogCacheRemoveListener (#12514) (#12643)
    
    **Cherry-pick Information:**
    - Original commit: 933401bda9da09fcfa1a5bf4048a92bce1ab8460
    - Target branch: `branch-1.3`
    - Status: ⚠️ **Has conflicts - manual resolution required**
    
    **Do not merge** until conflict markers are resolved and the
    `cherry-pick-conflict` label is removed.
    
    Please review and resolve the conflicts before merging.
    
    ---------
    
    Co-authored-by: Qi Yu <[email protected]>
---
 .../apache/gravitino/catalog/CatalogManager.java   |  6 +-
 .../gravitino/catalog/TestCatalogManager.java      | 64 +++++++++++++---------
 2 files changed, 44 insertions(+), 26 deletions(-)

diff --git 
a/core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java 
b/core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java
index 4df78ad21b..71f90b679a 100644
--- a/core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java
+++ b/core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java
@@ -53,6 +53,7 @@ import java.util.Properties;
 import java.util.ServiceLoader;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -297,7 +298,10 @@ public class CatalogManager implements CatalogDispatcher, 
Closeable {
   @Nullable private final CatalogChangeLogListener catalogChangeLogListener;
 
   private final IdGenerator idGenerator;
-  private final List<Consumer<NameIdentifier>> removalListeners = 
Lists.newArrayList();
+
+  // Copy-on-write: listeners may be registered while the cache's removal 
listener (running on a
+  // cache executor thread) is iterating this list.
+  private final List<Consumer<NameIdentifier>> removalListeners = new 
CopyOnWriteArrayList<>();
   private final ConcurrentHashMap<NameIdentifier, AtomicInteger> 
localMutationCounts =
       new ConcurrentHashMap<>();
 
diff --git 
a/core/src/test/java/org/apache/gravitino/catalog/TestCatalogManager.java 
b/core/src/test/java/org/apache/gravitino/catalog/TestCatalogManager.java
index 6d2ad36bf1..2101bd062e 100644
--- a/core/src/test/java/org/apache/gravitino/catalog/TestCatalogManager.java
+++ b/core/src/test/java/org/apache/gravitino/catalog/TestCatalogManager.java
@@ -122,6 +122,9 @@ public class TestCatalogManager {
   void reset() throws IOException {
     ((InMemoryEntityStore) entityStore).clear();
     entityStore.put(metalakeEntity, true);
+    // The shared CatalogManager is created once in @BeforeAll, so its cache 
would otherwise keep
+    // entries created by previously executed test methods and make tests 
order-dependent.
+    catalogManager.getCatalogCache().invalidateAll();
   }
 
   @AfterAll
@@ -1220,37 +1223,48 @@ public class TestCatalogManager {
   }
 
   @Test
-  public void testCatalogCacheRemoveListener() {
+  public void testCatalogCacheRemoveListener() throws IOException {
     NameIdentifier ident = NameIdentifier.of(metalake, "catalog");
     Map<String, String> props =
         ImmutableMap.of(
             PROPERTY_KEY1, "value1", PROPERTY_KEY2, "value2", 
PROPERTY_KEY5_PREFIX + "1", "value3");
 
-    // Create a catalog
-    catalogManager.createCatalog(ident, Catalog.Type.RELATIONAL, provider, 
"comment", props);
-
-    // Load the catalog to add it to the cache
-    catalogManager.loadCatalog(ident);
-    
Assertions.assertNotNull(catalogManager.getCatalogCache().getIfPresent(ident));
+    // Use a dedicated CatalogManager (and entity store) instead of the shared 
static one: the
+    // shared instance keeps cache entries and cache removal listeners 
registered by other test
+    // methods, which would make the assertions below depend on the test 
execution order.
+    EntityStore store = new InMemoryEntityStore();
+    store.initialize(config);
+    store.put(metalakeEntity, true);
 
-    // Add a listener to track removed catalogs
-    Set<NameIdentifier> removedCatalogs = Sets.newConcurrentHashSet();
-    catalogManager.addCatalogCacheRemoveListener(removedCatalogs::add);
-
-    // Invalidate the cache to trigger the removal listener
-    catalogManager.getCatalogCache().invalidate(ident);
-
-    // Wait for the async eviction to complete
-    await()
-        .atMost(Duration.ofSeconds(5))
-        .untilAsserted(
-            () -> {
-              Assertions.assertTrue(
-                  removedCatalogs.contains(ident),
-                  "Listener should be notified of catalog removal");
-              Assertions.assertEquals(
-                  1, removedCatalogs.size(), "Only one catalog should be 
removed");
-            });
+    try (CatalogManager manager = new CatalogManager(config, store, new 
RandomIdGenerator())) {
+      // Create a catalog
+      manager.createCatalog(ident, Catalog.Type.RELATIONAL, provider, 
"comment", props);
+
+      // Load the catalog to add it to the cache
+      manager.loadCatalog(ident);
+      Assertions.assertNotNull(manager.getCatalogCache().getIfPresent(ident));
+
+      // Add a listener to track removed catalogs
+      Set<NameIdentifier> removedCatalogs = Sets.newConcurrentHashSet();
+      manager.addCatalogCacheRemoveListener(removedCatalogs::add);
+
+      // Invalidate the cache to trigger the removal listener
+      manager.getCatalogCache().invalidate(ident);
+
+      // Wait for the async eviction to complete
+      await()
+          .atMost(Duration.ofSeconds(5))
+          .untilAsserted(
+              () -> {
+                Assertions.assertTrue(
+                    removedCatalogs.contains(ident),
+                    "Listener should be notified of catalog removal");
+                Assertions.assertEquals(
+                    1, removedCatalogs.size(), "Only one catalog should be 
removed");
+              });
+    } finally {
+      store.close();
+    }
   }
 
   private void testProperties(Map<String, String> expectedProps, Map<String, 
String> testProps) {

Reply via email to