wombatu-kun commented on code in PR #8962:
URL: https://github.com/apache/paimon/pull/8962#discussion_r3705148648


##########
paimon-common/src/main/java/org/apache/paimon/fs/PluginFileIO.java:
##########
@@ -108,14 +111,40 @@ public String createBlobPresignedUrl(
     }
 
     private FileIO fileIO(Path path) throws IOException {
-        if (lazyFileIO == null) {
+        // read into a local, close() may null the field at any point and 
callers dereference the
+        // result directly
+        FileIO fileIO = lazyFileIO;
+        if (fileIO == null) {
             synchronized (this) {
-                if (lazyFileIO == null) {
-                    lazyFileIO = wrap(() -> createFileIO(path));
+                if (closed) {
+                    throw new IOException("This FileIO is closed.");
+                }
+                fileIO = lazyFileIO;
+                if (fileIO == null) {
+                    fileIO = wrap(() -> createFileIO(path));
+                    lazyFileIO = fileIO;
                 }
             }
         }
-        return lazyFileIO;
+        return fileIO;
+    }
+
+    @Override
+    public void close() throws IOException {
+        FileIO fileIO;
+        synchronized (this) {
+            closed = true;
+            fileIO = lazyFileIO;
+            lazyFileIO = null;
+        }
+        if (fileIO != null) {
+            // the delegate lives in the plugin classloader, so close it under 
that classloader too
+            wrap(
+                    () -> {
+                        fileIO.close();

Review Comment:
   Done 57edbe84d. Cache values are reference counted now: the removal listener 
hands back only the cache's own reference, and the delegate is closed when the 
last lease goes, with leases held for the duration of each operation and for 
the lifetime of every returned stream.
   
   The window is wider than the entry count suggests - the admission policy can 
evict a just-inserted entry, so the caller's lease is taken before the put. 
`fileIO()` is deprecated because a raw reference carries no lifetime to track; 
`BaseMultiPartUploadCommitter`, `LanceUtils` and `VortexUtils` now work inside 
a lease instead.



##########
paimon-common/src/main/java/org/apache/paimon/fs/hadoop/HadoopFileIO.java:
##########
@@ -226,6 +259,64 @@ protected FileSystem 
createFileSystem(org.apache.hadoop.fs.Path path) throws IOE
         return fileSystem;
     }
 
+    /**
+     * Whether the {@link FileSystem} instances created for the given scheme 
belong to this {@link
+     * FileIO} exclusively, and may therefore be closed by it.
+     *
+     * <p>This mirrors the branch Hadoop itself takes in {@code 
FileSystem#get(URI, Configuration)}:
+     * with {@code fs.<scheme>.impl.disable.cache} set, Hadoop hands out a 
fresh instance that
+     * nobody else can reach, so releasing it is our responsibility. Otherwise 
the instance lives in
+     * Hadoop's global cache and is shared with every other user in this JVM, 
including other {@link
+     * FileIO}s and the compute engine itself; {@code FileSystem#closeAll} 
releases those on
+     * shutdown and closing one here would break unrelated readers.
+     *
+     * <p>The scheme is the one taken from the path, not from {@code 
FileSystem#getUri()}, and it is
+     * matched as written rather than lower cased, because that is what Hadoop 
looks up. Any
+     * deviation could report a cached, shared instance as owned.
+     */
+    @VisibleForTesting
+    boolean isOwnedScheme(@Nullable String scheme) {
+        if (hadoopConf == null) {
+            return false;
+        }
+        Configuration conf = hadoopConf.get();
+        if (scheme == null) {
+            // a path without a scheme is served by the default file system
+            try {
+                scheme = FileSystem.getDefaultUri(conf).getScheme();
+            } catch (IllegalArgumentException e) {
+                // a missing or malformed fs.defaultFS, so there is no scheme 
to claim ownership of
+                return false;
+            }
+        }
+        return conf.getBoolean(String.format("fs.%s.impl.disable.cache", 
scheme), false);
+    }
+
+    @Override
+    public void close() throws IOException {
+        List<FileSystem> owned = new ArrayList<>();
+        synchronized (this) {
+            closed = true;
+            Map<Pair<String, String>, FileSystem> map = fsMap;
+            if (map == null) {
+                return;
+            }
+            for (Map.Entry<Pair<String, String>, FileSystem> entry : 
map.entrySet()) {
+                if (isOwnedScheme(entry.getKey().getLeft())) {

Review Comment:
   Done 57edbe84d. The map holds `{fileSystem, ownedAtCreation}` and both the 
loser cleanup and `close()` read that bit. The same pass stops recording an 
externally injected file system as owned, releases the raw instance when the 
Kerberos wrapper fails, and moves the loser's close outside the monitor so a 
slow object store teardown cannot stall the other callers.



##########
paimon-common/src/main/java/org/apache/paimon/fs/FileIO.java:
##########
@@ -655,10 +660,16 @@ static FileIOLoader checkAccess(FileIOLoader fileIO, Path 
path, CatalogContext c
             return null;
         }
 
-        // check access
+        // check access, the probe is thrown away afterwards so it has to be 
released here: with
+        // the Hadoop file system cache disabled its exists() call creates a 
file system that no
+        // one else can reach
         FileIO io = fileIO.load(path);
-        io.configure(config);
-        io.exists(path);
+        try {
+            io.configure(config);
+            io.exists(path);
+        } finally {
+            IOUtils.closeQuietly(io);

Review Comment:
   Done 57edbe84d. `checkAccess` returns the instance it checked and 
`FileIO.get` hands that one back, which also stops the file system being built 
twice. That changed its return type from `FileIOLoader` to `FileIO`, a 
signature change on a `@Public` interface with no other callers in the repo. 
The selection is also wrapped now, because an unchecked failure from a loader's 
`requiredOptions()` or `getScheme()` would otherwise strand the checked 
instance.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to