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

ritesh 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 2dd528c9e2 HDDS-9295. Avoid overriding finalize() in CodecBuffer. 
(#5302)
2dd528c9e2 is described below

commit 2dd528c9e2c51e831a76f987d1eef332e3135bad
Author: Tsz-Wo Nicholas Sze <[email protected]>
AuthorDate: Wed Sep 20 23:34:12 2023 -0700

    HDDS-9295. Avoid overriding finalize() in CodecBuffer. (#5302)
---
 .../apache/hadoop/hdds/utils/db/CodecBuffer.java   | 54 +++++++++++++++++++---
 .../apache/hadoop/hdds/utils/db/CodecTestUtil.java |  2 +-
 .../hadoop/hdds/utils/db/TestLeakDetector.java     | 43 +++++++++++++++++
 .../container/common/TestBlockDeletingService.java |  2 +
 .../container/keyvalue/TestKeyValueContainer.java  |  2 +
 .../hadoop/hdds/utils/TestRDBSnapshotProvider.java |  2 +
 .../org/apache/hadoop/hdds/utils/db/TestCodec.java |  4 ++
 .../apache/hadoop/hdds/utils/db/TestRDBStore.java  |  2 +
 .../utils/db/TestRDBStoreCodecBufferIterator.java  |  1 +
 .../hdds/utils/db/TestTypedRDBTableStore.java      |  2 +
 .../apache/hadoop/ozone/MiniOzoneClusterImpl.java  |  9 +++-
 11 files changed, 114 insertions(+), 9 deletions(-)

diff --git 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/utils/db/CodecBuffer.java
 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/utils/db/CodecBuffer.java
index 6a404c3e75..7d16abb816 100644
--- 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/utils/db/CodecBuffer.java
+++ 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/utils/db/CodecBuffer.java
@@ -37,6 +37,7 @@ import java.nio.ByteBuffer;
 import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Function;
 import java.util.function.IntFunction;
 import java.util.function.ToIntFunction;
 
@@ -47,9 +48,43 @@ import static org.apache.hadoop.hdds.HddsUtils.getStackTrace;
  * A buffer used by {@link Codec}
  * for supporting RocksDB direct {@link ByteBuffer} APIs.
  */
-public final class CodecBuffer implements AutoCloseable {
+public class CodecBuffer implements AutoCloseable {
   public static final Logger LOG = LoggerFactory.getLogger(CodecBuffer.class);
 
+  /** To create {@link CodecBuffer} instances. */
+  private static class Factory {
+    private static volatile Function<ByteBuf, CodecBuffer> constructor
+        = CodecBuffer::new;
+    static void set(Function<ByteBuf, CodecBuffer> f) {
+      constructor = f;
+      LOG.info("Successfully set constructor to " + f);
+    }
+
+    static CodecBuffer newCodecBuffer(ByteBuf buf) {
+      return constructor.apply(buf);
+    }
+  }
+
+  /** To detect buffer leak. */
+  private static class LeakDetector {
+    static CodecBuffer newCodecBuffer(ByteBuf buf) {
+      return new CodecBuffer(buf) {
+        @Override
+        protected void finalize() {
+          detectLeaks();
+        }
+      };
+    }
+  }
+
+  /**
+   * Detect buffer leak in runtime.
+   * Note that there is a severe performance penalty for leak detection.
+   */
+  public static void enableLeakDetection() {
+    Factory.set(LeakDetector::newCodecBuffer);
+  }
+
   /** The size of a buffer. */
   public static class Capacity {
     private final Object name;
@@ -106,7 +141,7 @@ public final class CodecBuffer implements AutoCloseable {
    *                  the buffer's capacity can be increased if necessary.
    */
   static CodecBuffer allocate(int capacity, IntFunction<ByteBuf> allocator) {
-    return new CodecBuffer(allocator.apply(capacity));
+    return Factory.newCodecBuffer(allocator.apply(capacity));
   }
 
   /**
@@ -127,7 +162,7 @@ public final class CodecBuffer implements AutoCloseable {
 
   /** Wrap the given array. */
   public static CodecBuffer wrap(byte[] array) {
-    return new CodecBuffer(Unpooled.wrappedBuffer(array));
+    return Factory.newCodecBuffer(Unpooled.wrappedBuffer(array));
   }
 
   private static final AtomicInteger LEAK_COUNT = new AtomicInteger();
@@ -153,8 +188,16 @@ public final class CodecBuffer implements AutoCloseable {
     Preconditions.assertSame(expected, buf.refCnt(), "refCnt");
   }
 
-  @Override
-  protected void finalize() throws Throwable {
+  /**
+   * Detect buffer leak by asserting that the underlying buffer is released
+   * when this object is garbage collected.
+   * This method may be invoked inside the {@link #finalize()} method
+   * or using a {@link java.lang.ref.ReferenceQueue}.
+   * For performance reason, this class does not override {@link #finalize()}.
+   *
+   * @see #enableLeakDetection()
+   */
+  void detectLeaks() {
     // leak detection
     final int capacity = buf.capacity();
     if (!released.isDone() && capacity > 0) {
@@ -169,7 +212,6 @@ public final class CodecBuffer implements AutoCloseable {
         buf.release(refCnt);
       }
     }
-    super.finalize();
   }
 
   @Override
diff --git 
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/utils/db/CodecTestUtil.java
 
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/utils/db/CodecTestUtil.java
index 785737ff57..d139a4a72e 100644
--- 
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/utils/db/CodecTestUtil.java
+++ 
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/utils/db/CodecTestUtil.java
@@ -36,7 +36,7 @@ public final class CodecTestUtil {
   /**
    * Force gc to check leakage.
    */
-  static void gc() throws InterruptedException {
+  public static void gc() throws InterruptedException {
     // use WeakReference to detect gc
     Object obj = new Object();
     final WeakReference<Object> weakRef = new WeakReference<>(obj);
diff --git 
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/utils/db/TestLeakDetector.java
 
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/utils/db/TestLeakDetector.java
new file mode 100644
index 0000000000..42422d0dff
--- /dev/null
+++ 
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/utils/db/TestLeakDetector.java
@@ -0,0 +1,43 @@
+/*
+ * 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.hdds.utils.db;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test {@link CodecBuffer.LeakDetector}.
+ */
+public final class TestLeakDetector {
+  @Test
+  public void test() throws Exception {
+    CodecBuffer.enableLeakDetection();
+    // allocate a buffer and then release it.
+    CodecBuffer.allocateHeap(2).release();
+    // no leak at this point.
+    CodecTestUtil.gc();
+
+    // allocate a buffer but NOT release it.
+    CodecBuffer.allocateHeap(3);
+    // It should detect a buffer leak.
+    final AssertionError e = Assertions.assertThrows(
+        AssertionError.class, CodecTestUtil::gc);
+    e.printStackTrace(System.out);
+    Assertions.assertTrue(e.getMessage().startsWith("Found 1 leak"));
+  }
+}
diff --git 
a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/TestBlockDeletingService.java
 
b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/TestBlockDeletingService.java
index b4efd6a27c..080300bcb8 100644
--- 
a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/TestBlockDeletingService.java
+++ 
b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/common/TestBlockDeletingService.java
@@ -143,6 +143,8 @@ public class TestBlockDeletingService {
 
   @Before
   public void init() throws IOException {
+    CodecBuffer.enableLeakDetection();
+
     testRoot = GenericTestUtils
         .getTestDir(TestBlockDeletingService.class.getSimpleName());
     if (testRoot.exists()) {
diff --git 
a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/keyvalue/TestKeyValueContainer.java
 
b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/keyvalue/TestKeyValueContainer.java
index 6dd5b1411f..5c241e7cc5 100644
--- 
a/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/keyvalue/TestKeyValueContainer.java
+++ 
b/hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/keyvalue/TestKeyValueContainer.java
@@ -145,6 +145,8 @@ public class TestKeyValueContainer {
 
   @Before
   public void setUp() throws Exception {
+    CodecBuffer.enableLeakDetection();
+
     DatanodeConfiguration dc = CONF.getObject(DatanodeConfiguration.class);
     dc.setAutoCompactionSmallSstFileNum(100);
     dc.setRocksdbDeleteObsoleteFilesPeriod(5000);
diff --git 
a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/utils/TestRDBSnapshotProvider.java
 
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/utils/TestRDBSnapshotProvider.java
index 585c635d5a..7a1ebbdb7e 100644
--- 
a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/utils/TestRDBSnapshotProvider.java
+++ 
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/utils/TestRDBSnapshotProvider.java
@@ -88,6 +88,8 @@ public class TestRDBSnapshotProvider {
 
   @BeforeEach
   public void init(@TempDir File tempDir) throws Exception {
+    CodecBuffer.enableLeakDetection();
+
     options = getNewDBOptions();
     configSet = new HashSet<>();
     for (String name : families) {
diff --git 
a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/utils/db/TestCodec.java
 
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/utils/db/TestCodec.java
index cdcef15a86..1a0559d94f 100644
--- 
a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/utils/db/TestCodec.java
+++ 
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/utils/db/TestCodec.java
@@ -43,6 +43,10 @@ public final class TestCodec {
   static final Logger LOG = LoggerFactory.getLogger(TestCodec.class);
   static final int NUM_LOOPS = 10;
 
+  static {
+    CodecBuffer.enableLeakDetection();
+  }
+
   @Test
   public void testShortCodec() throws Exception {
     runTestShortCodec((short)0);
diff --git 
a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/utils/db/TestRDBStore.java
 
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/utils/db/TestRDBStore.java
index 7831e03efd..b0a71cea91 100644
--- 
a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/utils/db/TestRDBStore.java
+++ 
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/utils/db/TestRDBStore.java
@@ -75,6 +75,8 @@ public class TestRDBStore {
 
   @BeforeEach
   public void setUp(@TempDir File tempDir) throws Exception {
+    CodecBuffer.enableLeakDetection();
+
     options = new ManagedDBOptions();
     options.setCreateIfMissing(true);
     options.setCreateMissingColumnFamilies(true);
diff --git 
a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/utils/db/TestRDBStoreCodecBufferIterator.java
 
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/utils/db/TestRDBStoreCodecBufferIterator.java
index f9806b47d2..5069da4a71 100644
--- 
a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/utils/db/TestRDBStoreCodecBufferIterator.java
+++ 
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/utils/db/TestRDBStoreCodecBufferIterator.java
@@ -62,6 +62,7 @@ public class TestRDBStoreCodecBufferIterator {
 
   @BeforeEach
   public void setup() {
+    CodecBuffer.enableLeakDetection();
     rocksIteratorMock = mock(RocksIterator.class);
     managedRocksIterator = newManagedRocksIterator();
     rdbTableMock = mock(RDBTable.class);
diff --git 
a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/utils/db/TestTypedRDBTableStore.java
 
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/utils/db/TestTypedRDBTableStore.java
index cc249a57b7..5352c14e55 100644
--- 
a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/utils/db/TestTypedRDBTableStore.java
+++ 
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/utils/db/TestTypedRDBTableStore.java
@@ -65,6 +65,8 @@ public class TestTypedRDBTableStore {
 
   @BeforeEach
   public void setUp(@TempDir File tempDir) throws Exception {
+    CodecBuffer.enableLeakDetection();
+
     options = new ManagedDBOptions();
     options.setCreateIfMissing(true);
     options.setCreateMissingColumnFamilies(true);
diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/MiniOzoneClusterImpl.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/MiniOzoneClusterImpl.java
index 42b5402256..cd21063156 100644
--- 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/MiniOzoneClusterImpl.java
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/MiniOzoneClusterImpl.java
@@ -66,6 +66,7 @@ import 
org.apache.hadoop.hdds.security.symmetric.SecretKeyClient;
 import 
org.apache.hadoop.hdds.security.x509.certificate.client.CertificateClient;
 import org.apache.hadoop.hdds.utils.IOUtils;
 import org.apache.hadoop.hdds.utils.db.CodecBuffer;
+import org.apache.hadoop.hdds.utils.db.CodecTestUtil;
 import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksObjectMetrics;
 import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem;
 import org.apache.hadoop.ozone.client.OzoneClient;
@@ -120,6 +121,10 @@ public class MiniOzoneClusterImpl implements 
MiniOzoneCluster {
   private static final Logger LOG =
       LoggerFactory.getLogger(MiniOzoneClusterImpl.class);
 
+  static {
+    CodecBuffer.enableLeakDetection();
+  }
+
   private OzoneConfiguration conf;
   private final SCMConfigurator scmConfigurator;
   private StorageContainerManager scm;
@@ -449,8 +454,8 @@ public class MiniOzoneClusterImpl implements 
MiniOzoneCluster {
       DefaultMetricsSystem.shutdown();
 
       ManagedRocksObjectMetrics.INSTANCE.assertNoLeaks();
-      CodecBuffer.assertNoLeaks();
-    } catch (IOException e) {
+      CodecTestUtil.gc();
+    } catch (Exception e) {
       LOG.error("Exception while shutting down the cluster.", e);
     }
   }


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

Reply via email to