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 0d7e459d60 [#11406] fix(core): reload closed catalog cache wrapper 
(#11407)
0d7e459d60 is described below

commit 0d7e459d6026dcdbfe32e07b26766d42cc5febf7
Author: Qi Yu <[email protected]>
AuthorDate: Thu Jun 4 08:57:40 2026 +0800

    [#11406] fix(core): reload closed catalog cache wrapper (#11407)
    
    ### What changes were proposed in this pull request?
    
    This PR makes `CatalogManager.loadCatalogAndWrap` detect a cached
    `CatalogWrapper` whose underlying catalog has already been closed. When
    that happens, it invalidates the stale cache entry and reloads the
    wrapper from the entity store.
    
    It also adds a regression test that verifies `dropCatalog` can proceed
    after the cache contains a closed wrapper.
    
    ### Why are the changes needed?
    
    A closed `CatalogWrapper` sets its internal catalog reference to null.
    If a later `dropCatalog` call reuses that wrapper from cache, it can
    fail with a NullPointerException when checking catalog state or
    capabilities.
    
    Fix: #11406
    
    ### Does this PR introduce _any_ user-facing change?
    
    No user-facing API or property changes.
    
    ### How was this patch tested?
    
    ```bash
    ./gradlew :core:test --tests 
org.apache.gravitino.catalog.TestCatalogManager.testDropCatalogReloadsClosedCachedWrapper
 -PskipWeb=true -PskipDockerTests=true
    ./gradlew :core:test --tests 
org.apache.gravitino.catalog.TestCatalogManager -PskipWeb=true 
-PskipDockerTests=true
    git diff --check
    ```
---
 .../apache/gravitino/catalog/CatalogManager.java   | 13 ++++-
 .../gravitino/catalog/TestCatalogManager.java      | 60 ++++++++++++++++++++++
 2 files changed, 72 insertions(+), 1 deletion(-)

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 203b8c5555..91bd5b08b6 100644
--- a/core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java
+++ b/core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java
@@ -1001,13 +1001,24 @@ public class CatalogManager implements 
CatalogDispatcher, Closeable {
 
   /**
    * Loads the catalog with the specified identifier, wraps it in a 
CatalogWrapper, and caches the
-   * wrapper for reuse.
+   * wrapper for reuse. If the cached wrapper has already been closed (its 
underlying catalog is
+   * null), the stale entry is evicted and a fresh wrapper is loaded and 
cached.
    *
    * @param ident The identifier of the catalog to load.
    * @return The wrapped CatalogWrapper containing the loaded catalog.
    * @throws NoSuchCatalogException If the specified catalog does not exist.
    */
   public CatalogWrapper loadCatalogAndWrap(NameIdentifier ident) throws 
NoSuchCatalogException {
+    CatalogWrapper wrapper = catalogCache.get(ident, 
this::loadCatalogInternal);
+    if (wrapper.catalog() != null) {
+      return wrapper;
+    }
+
+    // The cached wrapper has already been closed (catalog() == null), e.g. by 
a prior
+    // dropCatalog or cache eviction. Evict the stale entry and reload a fresh 
one.
+    // Use a conditional remove so we do not clobber a wrapper that another 
thread may
+    // have concurrently reloaded into the cache between our initial get and 
this remove.
+    catalogCache.asMap().remove(ident, wrapper);
     return catalogCache.get(ident, this::loadCatalogInternal);
   }
 
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 5d978dd3b0..4880d7f258 100644
--- a/core/src/test/java/org/apache/gravitino/catalog/TestCatalogManager.java
+++ b/core/src/test/java/org/apache/gravitino/catalog/TestCatalogManager.java
@@ -1027,6 +1027,66 @@ public class TestCatalogManager {
     
Assertions.assertNull(catalogManager.getCatalogCache().getIfPresent(ident));
   }
 
+  @Test
+  void testDropCatalogReloadsClosedCachedWrapper() throws Exception {
+    NameIdentifier ident = NameIdentifier.of("metalake", 
"closed_cache_drop_test");
+    Map<String, String> props =
+        ImmutableMap.of(
+            "provider",
+            "test",
+            PROPERTY_KEY1,
+            "value1",
+            PROPERTY_KEY2,
+            "value2",
+            PROPERTY_KEY5_PREFIX + "1",
+            "value3");
+
+    Catalog catalog =
+        catalogManager.createCatalog(ident, Catalog.Type.RELATIONAL, provider, 
"comment", props);
+    Assertions.assertDoesNotThrow(() -> catalogManager.disableCatalog(ident));
+    CatalogEntity entity = entityStore.get(ident, EntityType.CATALOG, 
CatalogEntity.class);
+    FieldUtils.writeField(catalog, "entity", entity, true);
+
+    CatalogManager.CatalogWrapper closedWrapper = 
catalogManager.loadCatalogAndWrap(ident);
+    closedWrapper.close();
+    Assertions.assertSame(closedWrapper, 
catalogManager.getCatalogCache().getIfPresent(ident));
+
+    boolean dropped = catalogManager.dropCatalog(ident);
+
+    Assertions.assertTrue(dropped);
+    Assertions.assertFalse(entityStore.exists(ident, EntityType.CATALOG));
+    
Assertions.assertNull(catalogManager.getCatalogCache().getIfPresent(ident));
+  }
+
+  @Test
+  void testLoadCatalogAndWrapDoesNotInvalidateConcurrentlyReloadedWrapper() {
+    NameIdentifier ident = NameIdentifier.of("metalake", 
"concurrent_cache_reload_test");
+
+    CatalogManager.CatalogWrapper closedWrapper = 
Mockito.mock(CatalogManager.CatalogWrapper.class);
+    CatalogManager.CatalogWrapper freshWrapper = 
Mockito.mock(CatalogManager.CatalogWrapper.class);
+    BaseCatalog<?> freshCatalog = Mockito.mock(BaseCatalog.class);
+    Mockito.doReturn(freshCatalog).when(freshWrapper).catalog();
+    Mockito.doAnswer(
+            invocation -> {
+              catalogManager.getCatalogCache().put(ident, freshWrapper);
+              return null;
+            })
+        .when(closedWrapper)
+        .catalog();
+
+    try {
+      catalogManager.getCatalogCache().put(ident, closedWrapper);
+
+      CatalogManager.CatalogWrapper loadedWrapper = 
catalogManager.loadCatalogAndWrap(ident);
+
+      Assertions.assertSame(freshWrapper, loadedWrapper);
+      Assertions.assertSame(freshWrapper, 
catalogManager.getCatalogCache().getIfPresent(ident));
+      Mockito.verify(freshWrapper, Mockito.never()).close();
+    } finally {
+      catalogManager.getCatalogCache().invalidate(ident);
+    }
+  }
+
   @Test
   void testAlterMutableProperties() {
     NameIdentifier ident = NameIdentifier.of("metalake", "test51");

Reply via email to