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

mchades 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 ee0a57070b [#8953]Improve(gvfs-java): refactor gvfs cache (#8954)
ee0a57070b is described below

commit ee0a57070b86a872350be290005841ba898dc850
Author: Junda Yang <[email protected]>
AuthorDate: Wed Oct 29 01:10:02 2025 -0700

    [#8953]Improve(gvfs-java): refactor gvfs cache (#8954)
    
    ### What changes were proposed in this pull request?
    
    Refactored the FileSystem caching strategy in BaseGVFSOperations to
    cache by storage backend instead of by fileset.
    
    Main Changes:
    - New cache key: Changed from Pair<NameIdentifier, String> (fileset
    identifier + location name) to FileSystemCacheKey(scheme, authority,
    UserGroupInformation)
    - New FileSystemCacheKey inner class: Static inner class with proper
    equals()/hashCode() implementation based on filesystem scheme,
    authority, and user
    - Restructured caching logic: Moved FileSystem caching from
    getActualFileSystemByLocationName() to getActualFileSystemByPath(),
    separating fileset metadata resolution from filesystem instantiation
    
    ### Why are the changes needed?
    
    More granular caching at the actual filesystem level rather than virtual
    fileset level. Now getActualFileSystemByPath() load FS from cache.
    
    Fix: #8953
    
    ### Does this PR introduce _any_ user-facing change?
    
    No. This is an internal caching optimization. The external API and
    behavior remain unchanged.
    
    ### How was this patch tested?
    
    Unit tests
---
 .../filesystem/hadoop/BaseGVFSOperations.java      | 177 ++++++++++++++-------
 .../filesystem/hadoop/TestFileSystemCacheKey.java  | 116 ++++++++++++++
 .../gravitino/filesystem/hadoop/TestGvfsBase.java  |  91 +++++++++--
 3 files changed, 314 insertions(+), 70 deletions(-)

diff --git 
a/clients/filesystem-hadoop3/src/main/java/org/apache/gravitino/filesystem/hadoop/BaseGVFSOperations.java
 
b/clients/filesystem-hadoop3/src/main/java/org/apache/gravitino/filesystem/hadoop/BaseGVFSOperations.java
index 920ed90128..e61b6abc0f 100644
--- 
a/clients/filesystem-hadoop3/src/main/java/org/apache/gravitino/filesystem/hadoop/BaseGVFSOperations.java
+++ 
b/clients/filesystem-hadoop3/src/main/java/org/apache/gravitino/filesystem/hadoop/BaseGVFSOperations.java
@@ -44,6 +44,7 @@ import java.net.URI;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.ServiceLoader;
 import java.util.Set;
@@ -54,7 +55,6 @@ import java.util.stream.Collectors;
 import javax.annotation.Nullable;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.reflect.FieldUtils;
-import org.apache.commons.lang3.tuple.Pair;
 import org.apache.gravitino.Catalog;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.audit.CallerContext;
@@ -85,6 +85,7 @@ import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.fs.permission.FsPermission;
 import org.apache.hadoop.security.Credentials;
+import org.apache.hadoop.security.UserGroupInformation;
 import org.apache.hadoop.security.token.Token;
 import org.apache.hadoop.util.Progressable;
 import org.slf4j.Logger;
@@ -125,9 +126,7 @@ public abstract class BaseGVFSOperations implements 
Closeable {
 
   private final Configuration conf;
 
-  // Fileset nameIdentifier-locationName Pair and its corresponding FileSystem 
cache, the name
-  // identifier has four levels, the first level is metalake name.
-  private final Cache<Pair<NameIdentifier, String>, FileSystem> 
internalFileSystemCache;
+  private final Cache<FileSystemCacheKey, FileSystem> fileSystemCache;
 
   private final Map<String, FileSystemProvider> fileSystemProvidersMap;
 
@@ -140,6 +139,66 @@ public abstract class BaseGVFSOperations implements 
Closeable {
   private final boolean enableCredentialVending;
 
   private final boolean autoCreateLocation;
+  /** A key class for caching FileSystem instances based on scheme, authority, 
and configuration. */
+  public static class FileSystemCacheKey {
+    private final String scheme;
+    private final String authority;
+    private final UserGroupInformation ugi;
+
+    /**
+     * Constructor for FileSystemCacheKey.
+     *
+     * @param scheme the scheme of the filesystem
+     * @param authority the authority of the filesystem
+     * @param ugi the user group information
+     */
+    FileSystemCacheKey(String scheme, String authority, UserGroupInformation 
ugi) {
+      this.scheme = scheme;
+      this.authority = authority;
+      this.ugi = ugi;
+    }
+
+    /**
+     * Get the scheme of the filesystem.
+     *
+     * @return the scheme
+     */
+    public String scheme() {
+      return scheme;
+    }
+
+    /**
+     * Get the authority of the filesystem.
+     *
+     * @return the authority
+     */
+    public String authority() {
+      return authority;
+    }
+
+    /**
+     * Get the UserGroupInformation
+     *
+     * @return the UserGroupInformation
+     */
+    public UserGroupInformation ugi() {
+      return ugi;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (!(o instanceof FileSystemCacheKey)) return false;
+      FileSystemCacheKey that = (FileSystemCacheKey) o;
+      return Objects.equals(scheme, that.scheme)
+          && Objects.equals(authority, that.authority)
+          && Objects.equals(ugi, that.ugi);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(scheme, authority, ugi);
+    }
+  }
 
   /**
    * Constructs a new {@link BaseGVFSOperations} with the given {@link 
Configuration}.
@@ -159,7 +218,7 @@ public abstract class BaseGVFSOperations implements 
Closeable {
             FS_GRAVITINO_FILESET_METADATA_CACHE_ENABLE,
             FS_GRAVITINO_FILESET_METADATA_CACHE_ENABLE_DEFAULT);
 
-    this.internalFileSystemCache = newFileSystemCache(configuration);
+    this.fileSystemCache = newFileSystemCache(configuration);
 
     this.fileSystemProvidersMap = 
ImmutableMap.copyOf(getFileSystemProviders());
 
@@ -211,14 +270,14 @@ public abstract class BaseGVFSOperations implements 
Closeable {
   @Override
   public void close() throws IOException {
     // close all actual FileSystems
-    for (FileSystem fileSystem : internalFileSystemCache.asMap().values()) {
+    for (FileSystem fileSystem : fileSystemCache.asMap().values()) {
       try {
         fileSystem.close();
       } catch (IOException e) {
         // ignore
       }
     }
-    internalFileSystemCache.invalidateAll();
+    fileSystemCache.invalidateAll();
 
     try {
       if (filesetMetadataCache != null && filesetMetadataCache.isPresent()) {
@@ -378,7 +437,7 @@ public abstract class BaseGVFSOperations implements 
Closeable {
    */
   protected Token<?>[] addDelegationTokensForAllFS(String renewer, Credentials 
credentials) {
     List<Token<?>> tokenList = Lists.newArrayList();
-    for (FileSystem fileSystem : internalFileSystemCache.asMap().values()) {
+    for (FileSystem fileSystem : fileSystemCache.asMap().values()) {
       try {
         tokenList.addAll(Arrays.asList(fileSystem.addDelegationTokens(renewer, 
credentials)));
       } catch (IOException e) {
@@ -603,8 +662,8 @@ public abstract class BaseGVFSOperations implements 
Closeable {
   }
 
   @VisibleForTesting
-  Cache<Pair<NameIdentifier, String>, FileSystem> internalFileSystemCache() {
-    return internalFileSystemCache;
+  Cache<FileSystemCacheKey, FileSystem> internalFileSystemCache() {
+    return fileSystemCache;
   }
 
   /**
@@ -655,40 +714,25 @@ public abstract class BaseGVFSOperations implements 
Closeable {
     NameIdentifier catalogIdent =
         NameIdentifier.of(filesetIdent.namespace().level(0), 
filesetIdent.namespace().level(1));
     try {
-      return internalFileSystemCache.get(
-          Pair.of(filesetIdent, locationName),
-          cacheKey -> {
-            try {
-              Fileset fileset = getFileset(cacheKey.getLeft());
-              String targetLocationName =
-                  cacheKey.getRight() == null
-                      ? 
fileset.properties().get(PROPERTY_DEFAULT_LOCATION_NAME)
-                      : cacheKey.getRight();
-
-              Preconditions.checkArgument(
-                  fileset.storageLocations().containsKey(targetLocationName),
-                  "Location name: %s is not found in fileset: %s.",
-                  targetLocationName,
-                  cacheKey.getLeft());
-
-              Path targetLocation = new 
Path(fileset.storageLocations().get(targetLocationName));
-              Map<String, String> allProperties =
-                  getAllProperties(
-                      cacheKey.getLeft(), targetLocation.toUri().getScheme(), 
targetLocationName);
-
-              FileSystem actualFileSystem =
-                  getActualFileSystemByPath(targetLocation, allProperties);
-              createFilesetLocationIfNeed(cacheKey.getLeft(), 
actualFileSystem, targetLocation);
-              return actualFileSystem;
-            } catch (IOException ioe) {
-              throw new GravitinoRuntimeException(
-                  ioe,
-                  "Exception occurs when create new FileSystem for fileset: 
%s, location: %s, msg: %s",
-                  cacheKey.getLeft(),
-                  cacheKey.getRight(),
-                  ioe.getMessage());
-            }
-          });
+      Fileset fileset = getFileset(filesetIdent);
+      String targetLocationName =
+          locationName == null
+              ? fileset.properties().get(PROPERTY_DEFAULT_LOCATION_NAME)
+              : locationName;
+
+      Preconditions.checkArgument(
+          fileset.storageLocations().containsKey(targetLocationName),
+          "Location name: %s is not found in fileset: %s.",
+          targetLocationName,
+          filesetIdent);
+
+      Path targetLocation = new 
Path(fileset.storageLocations().get(targetLocationName));
+      Map<String, String> allProperties =
+          getAllProperties(filesetIdent, targetLocation.toUri().getScheme(), 
targetLocationName);
+
+      FileSystem actualFileSystem = getActualFileSystemByPath(targetLocation, 
allProperties);
+      createFilesetLocationIfNeed(filesetIdent, actualFileSystem, 
targetLocation);
+      return actualFileSystem;
     } catch (RuntimeException e) {
       Throwable cause = e.getCause();
       if (cause instanceof NoSuchCatalogException || cause instanceof 
CatalogNotInUseException) {
@@ -718,21 +762,43 @@ public abstract class BaseGVFSOperations implements 
Closeable {
     }
   }
 
-  private FileSystem getActualFileSystemByPath(
-      Path actualFilePath, Map<String, String> allProperties) throws 
IOException {
+  /**
+   * Get the actual file system by the given actual file path and properties.
+   *
+   * @param actualFilePath the actual file path.
+   * @param allProperties the properties.
+   * @return the actual file system.
+   */
+  protected FileSystem getActualFileSystemByPath(
+      Path actualFilePath, Map<String, String> allProperties) {
     URI uri = actualFilePath.toUri();
     String scheme = uri.getScheme();
     Preconditions.checkArgument(
         StringUtils.isNotBlank(scheme), "Scheme of the actual file location 
cannot be null.");
 
-    FileSystemProvider provider = getFileSystemProviderByScheme(scheme);
-
-    // Reset the FileSystem service loader to make sure the FileSystem will 
reload the
-    // service file systems, this is a temporary solution to fix the issue
-    // https://github.com/apache/gravitino/issues/5609
-    resetFileSystemServiceLoader(scheme);
-
-    return provider.getFileSystem(actualFilePath, allProperties);
+    UserGroupInformation ugi;
+    try {
+      ugi = UserGroupInformation.getCurrentUser();
+    } catch (IOException e) {
+      throw new GravitinoRuntimeException(
+          e, "Cannot get current user for path: %s", actualFilePath);
+    }
+    return fileSystemCache.get(
+        new FileSystemCacheKey(scheme, uri.getAuthority(), ugi),
+        cacheKey -> {
+          FileSystemProvider provider = getFileSystemProviderByScheme(scheme);
+
+          // Reset the FileSystem service loader to make sure the FileSystem 
will reload the
+          // service file systems, this is a temporary solution to fix the 
issue
+          // https://github.com/apache/gravitino/issues/5609
+          resetFileSystemServiceLoader(scheme);
+          try {
+            return provider.getFileSystem(actualFilePath, allProperties);
+          } catch (IOException e) {
+            throw new GravitinoRuntimeException(
+                e, "Cannot get FileSystem for path: %s", actualFilePath);
+          }
+        });
   }
 
   private void resetFileSystemServiceLoader(String fsScheme) {
@@ -753,8 +819,7 @@ public abstract class BaseGVFSOperations implements 
Closeable {
     }
   }
 
-  private Cache<Pair<NameIdentifier, String>, FileSystem> newFileSystemCache(
-      Configuration configuration) {
+  private Cache<FileSystemCacheKey, FileSystem> 
newFileSystemCache(Configuration configuration) {
     int maxCapacity =
         configuration.getInt(
             
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_FILESET_CACHE_MAX_CAPACITY_KEY,
diff --git 
a/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestFileSystemCacheKey.java
 
b/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestFileSystemCacheKey.java
new file mode 100644
index 0000000000..3837fd92d3
--- /dev/null
+++ 
b/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestFileSystemCacheKey.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.filesystem.hadoop;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+import java.io.IOException;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.junit.jupiter.api.Test;
+
+/** Unit tests for {@link BaseGVFSOperations.FileSystemCacheKey}. */
+public class TestFileSystemCacheKey {
+
+  @Test
+  public void testEqualityWithSameValues() throws IOException {
+    UserGroupInformation ugi1 = UserGroupInformation.getCurrentUser();
+    UserGroupInformation ugi2 = UserGroupInformation.getCurrentUser();
+
+    BaseGVFSOperations.FileSystemCacheKey key1 =
+        new BaseGVFSOperations.FileSystemCacheKey("hdfs", "namenode:8020", 
ugi1);
+    BaseGVFSOperations.FileSystemCacheKey key2 =
+        new BaseGVFSOperations.FileSystemCacheKey("hdfs", "namenode:8020", 
ugi2);
+
+    assertEquals(key1, key2);
+    assertEquals(key1.hashCode(), key2.hashCode());
+  }
+
+  @Test
+  public void testInequalityWithDifferentScheme() throws IOException {
+    UserGroupInformation ugi = UserGroupInformation.getCurrentUser();
+
+    BaseGVFSOperations.FileSystemCacheKey key1 =
+        new BaseGVFSOperations.FileSystemCacheKey("hdfs", "namenode:8020", 
ugi);
+    BaseGVFSOperations.FileSystemCacheKey key2 =
+        new BaseGVFSOperations.FileSystemCacheKey("s3a", "namenode:8020", ugi);
+
+    assertNotEquals(key1, key2);
+  }
+
+  @Test
+  public void testInequalityWithDifferentAuthority() throws IOException {
+    UserGroupInformation ugi = UserGroupInformation.getCurrentUser();
+
+    BaseGVFSOperations.FileSystemCacheKey key1 =
+        new BaseGVFSOperations.FileSystemCacheKey("hdfs", "namenode1:8020", 
ugi);
+    BaseGVFSOperations.FileSystemCacheKey key2 =
+        new BaseGVFSOperations.FileSystemCacheKey("hdfs", "namenode2:8020", 
ugi);
+
+    assertNotEquals(key1, key2);
+  }
+
+  @Test
+  public void testInequalityWithNullAuthority() throws IOException {
+    UserGroupInformation ugi = UserGroupInformation.getCurrentUser();
+
+    BaseGVFSOperations.FileSystemCacheKey key1 =
+        new BaseGVFSOperations.FileSystemCacheKey("file", null, ugi);
+    BaseGVFSOperations.FileSystemCacheKey key2 =
+        new BaseGVFSOperations.FileSystemCacheKey("file", "localhost", ugi);
+
+    assertNotEquals(key1, key2);
+  }
+
+  @Test
+  public void testEqualityWithBothNullAuthority() throws IOException {
+    UserGroupInformation ugi = UserGroupInformation.getCurrentUser();
+
+    BaseGVFSOperations.FileSystemCacheKey key1 =
+        new BaseGVFSOperations.FileSystemCacheKey("file", null, ugi);
+    BaseGVFSOperations.FileSystemCacheKey key2 =
+        new BaseGVFSOperations.FileSystemCacheKey("file", null, ugi);
+
+    assertEquals(key1, key2);
+    assertEquals(key1.hashCode(), key2.hashCode());
+  }
+
+  @Test
+  public void testGetters() throws IOException {
+    UserGroupInformation ugi = UserGroupInformation.getCurrentUser();
+
+    BaseGVFSOperations.FileSystemCacheKey key =
+        new BaseGVFSOperations.FileSystemCacheKey("hdfs", "namenode:8020", 
ugi);
+
+    assertEquals("hdfs", key.scheme());
+    assertEquals("namenode:8020", key.authority());
+    assertEquals(ugi, key.ugi());
+  }
+
+  @Test
+  public void testNotEqualsWithDifferentType() throws IOException {
+    UserGroupInformation ugi = UserGroupInformation.getCurrentUser();
+
+    BaseGVFSOperations.FileSystemCacheKey key =
+        new BaseGVFSOperations.FileSystemCacheKey("hdfs", "namenode:8020", 
ugi);
+
+    assertNotEquals(key, "not a FileSystemCacheKey");
+    assertNotEquals(key, null);
+  }
+}
diff --git 
a/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestGvfsBase.java
 
b/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestGvfsBase.java
index b3ae42dba4..53a04ddc65 100644
--- 
a/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestGvfsBase.java
+++ 
b/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestGvfsBase.java
@@ -33,7 +33,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -46,6 +46,7 @@ import static org.mockserver.model.HttpRequest.request;
 import static org.mockserver.model.HttpResponse.response;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
+import com.github.benmanes.caffeine.cache.Cache;
 import com.google.common.collect.ImmutableMap;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -60,9 +61,7 @@ import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.concurrent.TimeUnit;
-import org.apache.commons.lang3.tuple.Pair;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Version;
 import org.apache.gravitino.dto.AuditDTO;
@@ -343,15 +342,19 @@ public class TestGvfsBase extends GravitinoMockServerBase 
{
       buildMockResourceForCredential(filesetName, localPath.toString());
 
       FileSystemTestUtils.mkdirs(managedFilesetPath, gravitinoFileSystem);
-      FileSystem proxyLocalFs =
-          Objects.requireNonNull(
-              ((GravitinoVirtualFileSystem) gravitinoFileSystem)
-                  .getOperations()
-                  .internalFileSystemCache()
-                  .getIfPresent(
-                      Pair.of(
-                          NameIdentifier.of(metalakeName, catalogName, 
schemaName, "testFSCache"),
-                          null)));
+
+      // Verify the internal cache contains a FileSystem for the local scheme
+      Cache<BaseGVFSOperations.FileSystemCacheKey, FileSystem> cache =
+          ((GravitinoVirtualFileSystem) gravitinoFileSystem)
+              .getOperations()
+              .internalFileSystemCache();
+
+      // The cache should have one entry for the local filesystem
+      assertEquals(1, cache.asMap().size());
+
+      // Get the cached filesystem (should be a local filesystem)
+      FileSystem proxyLocalFs = cache.asMap().values().iterator().next();
+      assertNotNull(proxyLocalFs);
 
       String anotherFilesetName = "test_new_fs";
       Path diffLocalPath =
@@ -401,11 +404,71 @@ public class TestGvfsBase extends GravitinoMockServerBase 
{
                           .asMap()
                           .size()));
 
-      assertNull(
+      // Verify the cache is empty after eviction
+      assertTrue(
           ((GravitinoVirtualFileSystem) fs)
               .getOperations()
               .internalFileSystemCache()
-              .getIfPresent(Pair.of(NameIdentifier.of("file"), 
LOCATION_NAME_UNKNOWN)));
+              .asMap()
+              .isEmpty());
+    }
+  }
+
+  @Test
+  public void testCacheReuseAcrossMultipleFilesets() throws IOException {
+    // Create two different filesets that point to the same local directory
+    String fileset1Name = "fileset_cache_reuse_1";
+    String fileset2Name = "fileset_cache_reuse_2";
+
+    // Both filesets point to the same underlying local path
+    Path localPath =
+        FileSystemTestUtils.createLocalDirPrefix(catalogName, schemaName, 
"shared_cache");
+
+    Path filesetPath1 =
+        FileSystemTestUtils.createFilesetPath(catalogName, schemaName, 
fileset1Name, true);
+    Path filesetPath2 =
+        FileSystemTestUtils.createFilesetPath(catalogName, schemaName, 
fileset2Name, true);
+
+    String locationPath1 =
+        String.format(
+            "/api/metalakes/%s/catalogs/%s/schemas/%s/filesets/%s/location",
+            metalakeName, catalogName, schemaName, fileset1Name);
+    String locationPath2 =
+        String.format(
+            "/api/metalakes/%s/catalogs/%s/schemas/%s/filesets/%s/location",
+            metalakeName, catalogName, schemaName, fileset2Name);
+
+    try (FileSystem fs = filesetPath1.getFileSystem(conf)) {
+      // Mock server responses for both filesets pointing to the same location
+      FileLocationResponse fileLocationResponse = new 
FileLocationResponse(localPath.toString());
+      Map<String, String> queryParams = new HashMap<>();
+      queryParams.put("sub_path", "");
+
+      buildMockResource(Method.GET, locationPath1, queryParams, null, 
fileLocationResponse, SC_OK);
+      buildMockResource(Method.GET, locationPath2, queryParams, null, 
fileLocationResponse, SC_OK);
+      buildMockResourceForCredential(fileset1Name, localPath.toString());
+      buildMockResourceForCredential(fileset2Name, localPath.toString());
+
+      // Access first fileset
+      FileSystemTestUtils.mkdirs(filesetPath1, fs);
+
+      Cache<BaseGVFSOperations.FileSystemCacheKey, FileSystem> cache =
+          ((GravitinoVirtualFileSystem) 
fs).getOperations().internalFileSystemCache();
+
+      // Should have one cached filesystem for the local scheme
+      assertEquals(1, cache.asMap().size());
+      FileSystem cachedFs1 = cache.asMap().values().iterator().next();
+      assertNotNull(cachedFs1);
+
+      // Access second fileset pointing to the same location
+      FileSystemTestUtils.mkdirs(filesetPath2, fs);
+
+      // Should still have only one cached filesystem (reused)
+      assertEquals(1, cache.asMap().size());
+      FileSystem cachedFs2 = cache.asMap().values().iterator().next();
+
+      // Both should be the same instance since they share 
scheme/authority/user
+      assertSame(cachedFs1, cachedFs2, "FileSystem instances should be reused 
for same storage");
     }
   }
 

Reply via email to