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

adoroszlai pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/master by this push:
     new fcbf2e78e15 HDDS-14474. Close main-thread FileSystem in dfsg/dfsv 
freon generators (#10606)
fcbf2e78e15 is described below

commit fcbf2e78e159ba580ffc4c6dd2e7518e905b9e73
Author: Chi-Hsuan Huang <[email protected]>
AuthorDate: Fri Jun 26 00:39:11 2026 +0800

    HDDS-14474. Close main-thread FileSystem in dfsg/dfsv freon generators 
(#10606)
---
 .../hadoop/ozone/freon/HadoopFsGenerator.java      |  20 +--
 .../hadoop/ozone/freon/HadoopFsValidator.java      |  18 ++-
 .../ozone/freon/TestHadoopFsClientClose.java       | 138 +++++++++++++++++++++
 3 files changed, 163 insertions(+), 13 deletions(-)

diff --git 
a/hadoop-ozone/freon/src/main/java/org/apache/hadoop/ozone/freon/HadoopFsGenerator.java
 
b/hadoop-ozone/freon/src/main/java/org/apache/hadoop/ozone/freon/HadoopFsGenerator.java
index 17cab0e8f74..a4f3372847d 100644
--- 
a/hadoop-ozone/freon/src/main/java/org/apache/hadoop/ozone/freon/HadoopFsGenerator.java
+++ 
b/hadoop-ozone/freon/src/main/java/org/apache/hadoop/ozone/freon/HadoopFsGenerator.java
@@ -24,6 +24,7 @@
 import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.hdds.cli.HddsVersionProvider;
 import org.apache.hadoop.hdds.conf.StorageSize;
+import org.apache.hadoop.hdds.utils.IOUtils;
 import org.kohsuke.MetaInfServices;
 import picocli.CommandLine.Command;
 import picocli.CommandLine.Option;
@@ -72,16 +73,21 @@ public class HadoopFsGenerator extends 
HadoopBaseFreonGenerator
   public Void call() throws Exception {
     super.init();
 
-    Path file = new Path(getRootPath() + "/" + generateObjectName(0));
-    getFileSystem().mkdirs(file.getParent());
+    FileSystem fileSystem = getFileSystem();
+    try {
+      Path file = new Path(getRootPath() + "/" + generateObjectName(0));
+      fileSystem.mkdirs(file.getParent());
 
-    contentGenerator =
-        new ContentGenerator(fileSize.toBytes(), bufferSize, copyBufferSize,
-            flushOrSync);
+      contentGenerator =
+          new ContentGenerator(fileSize.toBytes(), bufferSize, copyBufferSize,
+              flushOrSync);
 
-    timer = getMetrics().timer("file-create");
+      timer = getMetrics().timer("file-create");
 
-    runTests(this::createFile);
+      runTests(this::createFile);
+    } finally {
+      IOUtils.closeQuietly(fileSystem);
+    }
 
     return null;
   }
diff --git 
a/hadoop-ozone/freon/src/main/java/org/apache/hadoop/ozone/freon/HadoopFsValidator.java
 
b/hadoop-ozone/freon/src/main/java/org/apache/hadoop/ozone/freon/HadoopFsValidator.java
index 08bbcc194ca..a4e6e89eb11 100644
--- 
a/hadoop-ozone/freon/src/main/java/org/apache/hadoop/ozone/freon/HadoopFsValidator.java
+++ 
b/hadoop-ozone/freon/src/main/java/org/apache/hadoop/ozone/freon/HadoopFsValidator.java
@@ -22,6 +22,7 @@
 import java.util.concurrent.Callable;
 import org.apache.commons.io.IOUtils;
 import org.apache.hadoop.fs.FSDataInputStream;
+import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.hdds.cli.HddsVersionProvider;
 import org.kohsuke.MetaInfServices;
@@ -55,14 +56,19 @@ public class HadoopFsValidator extends 
HadoopBaseFreonGenerator
   public Void call() throws Exception {
     super.init();
 
-    Path file = new Path(getRootPath() + "/" + generateObjectName(0));
-    try (FSDataInputStream stream = getFileSystem().open(file)) {
-      referenceDigest = getDigest(stream);
-    }
+    FileSystem fileSystem = getFileSystem();
+    try {
+      Path file = new Path(getRootPath() + "/" + generateObjectName(0));
+      try (FSDataInputStream stream = fileSystem.open(file)) {
+        referenceDigest = getDigest(stream);
+      }
 
-    timer = getMetrics().timer("file-read");
+      timer = getMetrics().timer("file-read");
 
-    runTests(this::validateFile);
+      runTests(this::validateFile);
+    } finally {
+      org.apache.hadoop.hdds.utils.IOUtils.closeQuietly(fileSystem);
+    }
 
     return null;
   }
diff --git 
a/hadoop-ozone/freon/src/test/java/org/apache/hadoop/ozone/freon/TestHadoopFsClientClose.java
 
b/hadoop-ozone/freon/src/test/java/org/apache/hadoop/ozone/freon/TestHadoopFsClientClose.java
new file mode 100644
index 00000000000..303fa6e0a4e
--- /dev/null
+++ 
b/hadoop-ozone/freon/src/test/java/org/apache/hadoop/ozone/freon/TestHadoopFsClientClose.java
@@ -0,0 +1,138 @@
+/*
+ * 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.hadoop.ozone.freon;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.Path;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.LocalFileSystem;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+/**
+ * Verifies that {@link HadoopFsGenerator} (dfsg) and {@link HadoopFsValidator}
+ * (dfsv) close the {@link org.apache.hadoop.fs.FileSystem} instance they open 
on
+ * the main (calling) thread, in addition to the per-worker instances closed by
+ * {@code taskLoopCompleted()}. Before HDDS-14474 the main-thread instance 
leaked.
+ */
+public class TestHadoopFsClientClose {
+
+  @TempDir
+  private Path tempDir;
+
+  private String rootPath;
+
+  @BeforeEach
+  void setUp() {
+    CountingFileSystem.reset();
+    rootPath = "file://" + tempDir.toAbsolutePath();
+  }
+
+  @Test
+  void generatorClosesEveryFileSystem() {
+    int exitCode = runFreon("dfsg",
+        "-n", "4",
+        "-t", "2",
+        "-s", "1KB",
+        "--buffer", "1024",
+        "--copy-buffer", "1024");
+
+    assertEquals(0, exitCode);
+    assertEquals(CountingFileSystem.opened(), CountingFileSystem.closed(),
+        "Every FileSystem opened by dfsg must be closed");
+  }
+
+  @Test
+  void validatorClosesEveryFileSystem() {
+    // dfsv reads files written by dfsg, so generate them first under a shared
+    // prefix so both commands address the same object names.
+    assertEquals(0, runFreon("dfsg",
+        "-p", "fsleak",
+        "-n", "4",
+        "-t", "2",
+        "-s", "1KB",
+        "--buffer", "1024",
+        "--copy-buffer", "1024"));
+
+    CountingFileSystem.reset();
+
+    int exitCode = runFreon("dfsv",
+        "-p", "fsleak",
+        "-n", "4",
+        "-t", "2");
+
+    assertEquals(0, exitCode);
+    assertEquals(CountingFileSystem.opened(), CountingFileSystem.closed(),
+        "Every FileSystem opened by dfsv must be closed");
+  }
+
+  private int runFreon(String command, String... args) {
+    String[] prefix = {
+        "-D", "fs.file.impl=" + CountingFileSystem.class.getName(),
+        command, "-r", rootPath};
+    String[] argv = new String[prefix.length + args.length];
+    System.arraycopy(prefix, 0, argv, 0, prefix.length);
+    System.arraycopy(args, 0, argv, prefix.length, args.length);
+    return new Freon().getCmd().execute(argv);
+  }
+
+  /**
+   * A {@link LocalFileSystem} that counts how many instances are initialized 
and
+   * closed, so a leak of the main-thread instance is observable.
+   */
+  public static final class CountingFileSystem extends LocalFileSystem {
+
+    private static final AtomicInteger OPENED = new AtomicInteger();
+    private static final AtomicInteger CLOSED = new AtomicInteger();
+
+    private boolean counted;
+
+    @Override
+    public void initialize(URI name, Configuration conf) throws IOException {
+      super.initialize(name, conf);
+      OPENED.incrementAndGet();
+    }
+
+    @Override
+    public void close() throws IOException {
+      if (!counted) {
+        counted = true;
+        CLOSED.incrementAndGet();
+      }
+      super.close();
+    }
+
+    static void reset() {
+      OPENED.set(0);
+      CLOSED.set(0);
+    }
+
+    static int opened() {
+      return OPENED.get();
+    }
+
+    static int closed() {
+      return CLOSED.get();
+    }
+  }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to