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

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


The following commit(s) were added to refs/heads/main by this push:
     new 3d068320f6 [#13296] test(core): cover that force-dropping a metalake 
closes its catalogs (#13297)
3d068320f6 is described below

commit 3d068320f60eb2ed6c6c0de63bc0d78148153712
Author: Qi Yu <[email protected]>
AuthorDate: Fri Sep 18 14:02:36 2026 +0800

    [#13296] test(core): cover that force-dropping a metalake closes its 
catalogs (#13297)
    
    ### What changes were proposed in this pull request?
    
    Add a regression test
    `TestMetalakeManager#testForceDropMetalakeClosesCachedCatalogs` that
    uses a real `CatalogManager`, creates and loads a catalog under a
    metalake, force-drops the metalake and asserts that the catalog's
    `CatalogWrapper` is evicted from the catalog cache and closed (its
    catalog reference is released).
    
    `main` already force-drops child catalogs through
    `CatalogManager.dropCatalog` in `MetalakeManager.dropMetalake` (added as
    part of #12420), but nothing pinned that behaviour; `branch-1.3` lacks
    the logic entirely and leaks every catalog's connection pool when a
    metalake is dropped. The cherry-pick of this PR to `branch-1.3` carries
    the backport of the catalog-drop logic together with this test.
    
    ### Why are the changes needed?
    
    Dropping a metalake must release the resources of its catalogs (for
    example JDBC connection pools) exactly as dropping each catalog does.
    Without this, a metalake with many JDBC catalogs on one PostgreSQL
    server leaves its connections held until cache expiry or server restart.
    
    Fix: #13296
    
    ### Does this PR introduce _any_ user-facing change?
    
    No.
    
    ### How was this patch tested?
    
    New unit test `testForceDropMetalakeClosesCachedCatalogs` in
    `TestMetalakeManager`. It passes on `main` and fails on `branch-1.3`
    without the backported fix (the wrapper stays cached). Ran
    `TestMetalakeManager` and `TestCatalogManager` locally.
---
 .../gravitino/metalake/TestMetalakeManager.java    | 49 ++++++++++++++++++++++
 1 file changed, 49 insertions(+)

diff --git 
a/core/src/test/java/org/apache/gravitino/metalake/TestMetalakeManager.java 
b/core/src/test/java/org/apache/gravitino/metalake/TestMetalakeManager.java
index 862bef68d7..a9a69ec126 100644
--- a/core/src/test/java/org/apache/gravitino/metalake/TestMetalakeManager.java
+++ b/core/src/test/java/org/apache/gravitino/metalake/TestMetalakeManager.java
@@ -21,6 +21,7 @@ package org.apache.gravitino.metalake;
 import static org.apache.gravitino.Configs.TREE_LOCK_CLEAN_INTERVAL;
 import static org.apache.gravitino.Configs.TREE_LOCK_MAX_NODE_IN_MEMORY;
 import static org.apache.gravitino.Configs.TREE_LOCK_MIN_NODE_IN_MEMORY;
+import static org.awaitility.Awaitility.await;
 import static org.mockito.Mockito.doReturn;
 
 import com.google.common.collect.ImmutableMap;
@@ -30,9 +31,12 @@ import java.time.Instant;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 import org.apache.commons.lang3.reflect.FieldUtils;
 import org.apache.gravitino.Catalog;
 import org.apache.gravitino.Config;
+import org.apache.gravitino.Configs;
+import org.apache.gravitino.Entity;
 import org.apache.gravitino.Entity.EntityType;
 import org.apache.gravitino.EntityStore;
 import org.apache.gravitino.GravitinoEnv;
@@ -50,6 +54,7 @@ import org.apache.gravitino.lock.LockManager;
 import org.apache.gravitino.meta.AuditInfo;
 import org.apache.gravitino.meta.BaseMetalake;
 import org.apache.gravitino.meta.CatalogEntity;
+import org.apache.gravitino.secret.SecretManager;
 import org.apache.gravitino.storage.RandomIdGenerator;
 import org.apache.gravitino.storage.memory.TestMemoryEntityStore;
 import 
org.apache.gravitino.storage.memory.TestMemoryEntityStore.InMemoryEntityStore;
@@ -265,6 +270,50 @@ public class TestMetalakeManager {
     metalakeManager.dropMetalake(ident3, true);
   }
 
+  @Test
+  public void testForceDropMetalakeClosesCachedCatalogs() throws Exception {
+    // Dropping a metalake must release the resources of its catalogs (for 
example JDBC
+    // connection pools) exactly as dropping each catalog does, instead of 
leaving the cached
+    // catalog instances alive until cache expiry.
+    Config catalogConfig = new Config(false) {};
+    catalogConfig.set(Configs.CATALOG_LOAD_ISOLATED, false);
+    InMemoryEntityStore store = new InMemoryEntityStore();
+    store.initialize(catalogConfig);
+    CatalogManager catalogManager =
+        new CatalogManager(
+            catalogConfig, store, new RandomIdGenerator(), new 
SecretManager(catalogConfig));
+    MetalakeManager manager = new MetalakeManager(store, new 
RandomIdGenerator(), catalogManager);
+
+    NameIdentifier metalakeIdent = 
NameIdentifier.of("force_drop_closes_catalogs_ml");
+    manager.createMetalake(metalakeIdent, "comment", ImmutableMap.of());
+    NameIdentifier catalogIdent = NameIdentifier.of(metalakeIdent.name(), 
"cached_catalog");
+    catalogManager.createCatalog(
+        catalogIdent,
+        Catalog.Type.RELATIONAL,
+        "test",
+        "comment",
+        ImmutableMap.of(
+            "provider", "test", "key1", "value1", "key2", "value2", "key5-1", 
"value3"));
+    // createCatalog caches the wrapper; loadCatalog keeps it warm the same 
way a schema listing
+    // against the catalog would.
+    catalogManager.loadCatalog(catalogIdent);
+    CatalogManager.CatalogWrapper wrapper =
+        catalogManager.getCatalogCache().getIfPresent(catalogIdent);
+    Assertions.assertNotNull(wrapper);
+    Assertions.assertNotNull(wrapper.catalog());
+
+    Assertions.assertTrue(manager.dropMetalake(metalakeIdent, true));
+
+    
Assertions.assertNull(catalogManager.getCatalogCache().getIfPresent(catalogIdent));
+    Assertions.assertFalse(store.exists(catalogIdent, 
Entity.EntityType.CATALOG));
+    // The cache removal listener retires the wrapper asynchronously; once 
cleaned up, the wrapper
+    // drops its catalog reference.
+    await().atMost(10, TimeUnit.SECONDS).until(() -> wrapper.catalog() == 
null);
+
+    catalogManager.close();
+    store.close();
+  }
+
   @Test
   public void testForceDropMetalakeAfterDisableDropsLeftoverCatalogs() throws 
Exception {
     // Mirrors IT tearDown: disableMetalake then dropMetalake(force=true) 
while a catalog entity

Reply via email to