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

jt2594838 pushed a commit to branch dev/1.3
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/dev/1.3 by this push:
     new 4052320c6c0 Fixed UDF jar metadata handling in `UDFInfo` when multiple 
UDFs share the same jar (#17732) (#17835)
4052320c6c0 is described below

commit 4052320c6c031c634f924ebe2d8c32f8cf1d3629
Author: Caideyipi <[email protected]>
AuthorDate: Thu Jun 4 10:32:02 2026 +0800

    Fixed UDF jar metadata handling in `UDFInfo` when multiple UDFs share the 
same jar (#17732) (#17835)
    
    * UDF Fix
    
    * sp
    
    * fix
    
    * Filter invalid jar reference counts in snapshots
---
 .../iotdb/confignode/persistence/UDFInfo.java      |  54 ++++----
 .../iotdb/confignode/persistence/UDFInfoTest.java  |  71 +++++++++--
 .../executable/ReferenceCountedJarMetaKeeper.java  | 136 +++++++++++++++++++++
 .../meta/ConfigNodePipePluginMetaKeeper.java       |  50 ++------
 .../ReferenceCountedJarMetaKeeperTest.java         | 116 ++++++++++++++++++
 5 files changed, 352 insertions(+), 75 deletions(-)

diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/UDFInfo.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/UDFInfo.java
index 1c7682dd35b..4b80a2625f0 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/UDFInfo.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/UDFInfo.java
@@ -21,6 +21,7 @@ package org.apache.iotdb.confignode.persistence;
 
 import org.apache.iotdb.common.rpc.thrift.TSStatus;
 import org.apache.iotdb.commons.executable.ExecutableManager;
+import org.apache.iotdb.commons.executable.ReferenceCountedJarMetaKeeper;
 import org.apache.iotdb.commons.snapshot.SnapshotProcessor;
 import org.apache.iotdb.commons.udf.UDFInformation;
 import org.apache.iotdb.commons.udf.UDFTable;
@@ -37,7 +38,6 @@ import org.apache.iotdb.consensus.common.DataSet;
 import org.apache.iotdb.rpc.TSStatusCode;
 import org.apache.iotdb.udf.api.exception.UDFManagementException;
 
-import org.apache.tsfile.utils.ReadWriteIOUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -50,7 +50,6 @@ import java.io.OutputStream;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.locks.ReentrantLock;
@@ -63,7 +62,7 @@ public class UDFInfo implements SnapshotProcessor {
       ConfigNodeDescriptor.getInstance().getConf();
 
   private final UDFTable udfTable;
-  private final Map<String, String> existedJarToMD5;
+  private final ReferenceCountedJarMetaKeeper jarMetaKeeper;
 
   private final UDFExecutableManager udfExecutableManager;
 
@@ -73,7 +72,7 @@ public class UDFInfo implements SnapshotProcessor {
 
   public UDFInfo() throws IOException {
     udfTable = new UDFTable();
-    existedJarToMD5 = new HashMap<>();
+    jarMetaKeeper = new ReferenceCountedJarMetaKeeper();
     udfExecutableManager =
         UDFExecutableManager.setupAndGetInstance(
             CONFIG_NODE_CONF.getUdfTemporaryLibDir(), 
CONFIG_NODE_CONF.getUdfDir());
@@ -97,7 +96,8 @@ public class UDFInfo implements SnapshotProcessor {
           String.format("Failed to create UDF [%s], the same name UDF has been 
created", udfName));
     }
 
-    if (existedJarToMD5.containsKey(jarName) && 
!existedJarToMD5.get(jarName).equals(jarMD5)) {
+    if (jarMetaKeeper.containsJar(jarName)
+        && !jarMetaKeeper.jarNameExistsAndMatchesMd5(jarName, jarMD5)) {
       throw new UDFManagementException(
           String.format(
               "Failed to create UDF [%s], the same name Jar [%s] but different 
MD5 [%s] has existed",
@@ -115,7 +115,7 @@ public class UDFInfo implements SnapshotProcessor {
   }
 
   public boolean needToSaveJar(String jarName) {
-    return !existedJarToMD5.containsKey(jarName);
+    return jarMetaKeeper.needToSaveJar(jarName);
   }
 
   public TSStatus addUDFInTable(CreateFunctionPlan physicalPlan) {
@@ -123,7 +123,7 @@ public class UDFInfo implements SnapshotProcessor {
       final UDFInformation udfInformation = physicalPlan.getUdfInformation();
       udfTable.addUDFInformation(udfInformation.getFunctionName(), 
udfInformation);
       if (udfInformation.isUsingURI()) {
-        existedJarToMD5.put(udfInformation.getJarName(), 
udfInformation.getJarMD5());
+        addJarReference(udfInformation.getJarName(), 
udfInformation.getJarMD5());
         if (physicalPlan.getJarFile() != null) {
           udfExecutableManager.saveToInstallDir(
               ByteBuffer.wrap(physicalPlan.getJarFile().getValues()), 
udfInformation.getJarName());
@@ -168,7 +168,10 @@ public class UDFInfo implements SnapshotProcessor {
   public TSStatus dropFunction(DropFunctionPlan req) {
     String udfName = req.getFunctionName();
     if (udfTable.containsUDF(udfName)) {
-      existedJarToMD5.remove(udfTable.getUDFInformation(udfName).getJarName());
+      final UDFInformation udfInformation = 
udfTable.getUDFInformation(udfName);
+      if (udfInformation.isUsingURI()) {
+        removeJarReference(udfInformation.getJarName());
+      }
       udfTable.removeUDFInformation(udfName);
     }
     return new TSStatus(TSStatusCode.SUCCESS_STATUS.getStatusCode());
@@ -181,7 +184,7 @@ public class UDFInfo implements SnapshotProcessor {
 
   @TestOnly
   public Map<String, String> getRawExistedJarToMD5() {
-    return existedJarToMD5;
+    return jarMetaKeeper.getJarNameToMd5Map();
   }
 
   @Override
@@ -225,30 +228,39 @@ public class UDFInfo implements SnapshotProcessor {
       deserializeExistedJarToMD5(fileInputStream);
 
       udfTable.deserializeUDFTable(fileInputStream);
+      rebuildJarMetadataFromUDFTable();
     } finally {
       releaseUDFTableLock();
     }
   }
 
   public void serializeExistedJarToMD5(OutputStream outputStream) throws 
IOException {
-    ReadWriteIOUtils.write(existedJarToMD5.size(), outputStream);
-    for (Map.Entry<String, String> entry : existedJarToMD5.entrySet()) {
-      ReadWriteIOUtils.write(entry.getKey(), outputStream);
-      ReadWriteIOUtils.write(entry.getValue(), outputStream);
-    }
+    jarMetaKeeper.serializeJarNameToMd5(outputStream);
   }
 
   public void deserializeExistedJarToMD5(InputStream inputStream) throws 
IOException {
-    int size = ReadWriteIOUtils.readInt(inputStream);
-    while (size > 0) {
-      existedJarToMD5.put(
-          ReadWriteIOUtils.readString(inputStream), 
ReadWriteIOUtils.readString(inputStream));
-      size--;
-    }
+    jarMetaKeeper.deserializeJarNameToMd5(inputStream);
   }
 
   public void clear() {
-    existedJarToMD5.clear();
+    jarMetaKeeper.clear();
     udfTable.clear();
   }
+
+  private void addJarReference(String jarName, String jarMD5) {
+    jarMetaKeeper.addReference(jarName, jarMD5);
+  }
+
+  private void removeJarReference(String jarName) {
+    jarMetaKeeper.removeReference(jarName);
+  }
+
+  private void rebuildJarMetadataFromUDFTable() {
+    jarMetaKeeper.clear();
+    for (UDFInformation udfInformation : 
udfTable.getAllNonBuiltInUDFInformation()) {
+      if (udfInformation.isUsingURI()) {
+        addJarReference(udfInformation.getJarName(), 
udfInformation.getJarMD5());
+      }
+    }
+  }
 }
diff --git 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/UDFInfoTest.java
 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/UDFInfoTest.java
index 708ea779fad..c15cd064228 100644
--- 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/UDFInfoTest.java
+++ 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/UDFInfoTest.java
@@ -18,12 +18,12 @@
  */
 package org.apache.iotdb.confignode.persistence;
 
-import org.apache.iotdb.commons.exception.IllegalPathException;
 import org.apache.iotdb.commons.udf.UDFInformation;
 import 
org.apache.iotdb.confignode.consensus.request.write.function.CreateFunctionPlan;
+import 
org.apache.iotdb.confignode.consensus.request.write.function.DropFunctionPlan;
+import org.apache.iotdb.udf.api.exception.UDFManagementException;
 
 import org.apache.commons.io.FileUtils;
-import org.apache.thrift.TException;
 import org.apache.tsfile.utils.Binary;
 import org.junit.AfterClass;
 import org.junit.Assert;
@@ -37,6 +37,10 @@ import static 
org.apache.iotdb.db.utils.constant.TestConstant.BASE_OUTPUT_PATH;
 
 public class UDFInfoTest {
 
+  private static final String SHARED_JAR_NAME = "shared.jar";
+  private static final String SHARED_JAR_MD5 = "12345";
+  private static final String DIFFERENT_JAR_MD5 = "54321";
+
   private static UDFInfo udfInfo;
   private static UDFInfo udfInfoSaveBefore;
   private static final File snapshotDir = new File(BASE_OUTPUT_PATH, 
"snapshot");
@@ -59,18 +63,42 @@ public class UDFInfoTest {
   }
 
   @Test
-  public void testSnapshot() throws TException, IOException, 
IllegalPathException {
-    UDFInformation udfInformation =
-        new UDFInformation("test1", "test1", false, true, "test1.jar", 
"12345");
-    CreateFunctionPlan createFunctionPlan =
-        new CreateFunctionPlan(udfInformation, new Binary(new byte[] {1, 2, 
3}));
-    udfInfo.addUDFInTable(createFunctionPlan);
-    udfInfoSaveBefore.addUDFInTable(createFunctionPlan);
+  public void testDropOneSharedJarReferenceKeepsJarMetadata()
+      throws IOException, UDFManagementException {
+    clearUdfInfos();
+
+    udfInfo.addUDFInTable(createFunctionPlan("test1", SHARED_JAR_NAME, 
SHARED_JAR_MD5, true));
+    udfInfo.addUDFInTable(createFunctionPlan("test2", SHARED_JAR_NAME, 
SHARED_JAR_MD5, false));
+
+    udfInfo.dropFunction(new DropFunctionPlan("TEST1"));
+
+    Assert.assertFalse(udfInfo.needToSaveJar(SHARED_JAR_NAME));
+    Assert.assertEquals(1, udfInfo.getRawExistedJarToMD5().size());
+    Assert.assertEquals(SHARED_JAR_MD5, 
udfInfo.getRawExistedJarToMD5().get(SHARED_JAR_NAME));
+
+    udfInfo.validate("TEST3", SHARED_JAR_NAME, SHARED_JAR_MD5);
+    try {
+      udfInfo.validate("TEST3", SHARED_JAR_NAME, DIFFERENT_JAR_MD5);
+      Assert.fail("Expected shared jar conflict after dropping only one 
referenced UDF.");
+    } catch (UDFManagementException e) {
+      Assert.assertTrue(e.getMessage().contains("different MD5"));
+    }
+  }
+
+  @Test
+  public void testSnapshotRebuildsSharedJarReferences() throws IOException {
+    clearUdfInfos();
+    FileUtils.cleanDirectory(snapshotDir);
+
+    CreateFunctionPlan createFunctionPlan1 =
+        createFunctionPlan("test1", SHARED_JAR_NAME, SHARED_JAR_MD5, true);
+    CreateFunctionPlan createFunctionPlan2 =
+        createFunctionPlan("test2", SHARED_JAR_NAME, SHARED_JAR_MD5, false);
 
-    udfInformation = new UDFInformation("test2", "test2", false, true, 
"test2.jar", "123456");
-    createFunctionPlan = new CreateFunctionPlan(udfInformation, new Binary(new 
byte[] {1, 2, 3}));
-    udfInfo.addUDFInTable(createFunctionPlan);
-    udfInfoSaveBefore.addUDFInTable(createFunctionPlan);
+    udfInfo.addUDFInTable(createFunctionPlan1);
+    udfInfo.addUDFInTable(createFunctionPlan2);
+    udfInfoSaveBefore.addUDFInTable(createFunctionPlan1);
+    udfInfoSaveBefore.addUDFInTable(createFunctionPlan2);
 
     udfInfo.processTakeSnapshot(snapshotDir);
     udfInfo.clear();
@@ -78,5 +106,22 @@ public class UDFInfoTest {
 
     Assert.assertEquals(udfInfoSaveBefore.getRawExistedJarToMD5(), 
udfInfo.getRawExistedJarToMD5());
     Assert.assertEquals(udfInfoSaveBefore.getRawUDFTable(), 
udfInfo.getRawUDFTable());
+
+    udfInfo.dropFunction(new DropFunctionPlan("TEST1"));
+    Assert.assertFalse(udfInfo.needToSaveJar(SHARED_JAR_NAME));
+    Assert.assertEquals(SHARED_JAR_MD5, 
udfInfo.getRawExistedJarToMD5().get(SHARED_JAR_NAME));
+  }
+
+  private static void clearUdfInfos() {
+    udfInfo.clear();
+    udfInfoSaveBefore.clear();
+  }
+
+  private static CreateFunctionPlan createFunctionPlan(
+      String functionName, String jarName, String jarMD5, boolean 
includeJarFile) {
+    UDFInformation udfInformation =
+        new UDFInformation(functionName, functionName, false, true, jarName, 
jarMD5);
+    return new CreateFunctionPlan(
+        udfInformation, includeJarFile ? new Binary(new byte[] {1, 2, 3}) : 
null);
   }
 }
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/executable/ReferenceCountedJarMetaKeeper.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/executable/ReferenceCountedJarMetaKeeper.java
new file mode 100644
index 00000000000..52102aa96f2
--- /dev/null
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/executable/ReferenceCountedJarMetaKeeper.java
@@ -0,0 +1,136 @@
+/*
+ * 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.iotdb.commons.executable;
+
+import org.apache.tsfile.utils.ReadWriteIOUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+public class ReferenceCountedJarMetaKeeper {
+
+  private final Map<String, String> jarNameToMd5Map = new HashMap<>();
+  private final Map<String, Integer> jarNameToReferenceCountMap = new 
HashMap<>();
+
+  public synchronized boolean containsJar(final String jarName) {
+    return jarNameToMd5Map.containsKey(jarName);
+  }
+
+  public synchronized boolean needToSaveJar(final String jarName) {
+    return !containsJar(jarName);
+  }
+
+  public synchronized boolean jarNameExistsAndMatchesMd5(final String jarName, 
final String md5) {
+    return containsJar(jarName) && 
Objects.equals(jarNameToMd5Map.get(jarName), md5);
+  }
+
+  public synchronized void addReference(final String jarName, final String 
md5) {
+    if (jarNameToReferenceCountMap.containsKey(jarName)) {
+      jarNameToReferenceCountMap.put(jarName, 
jarNameToReferenceCountMap.get(jarName) + 1);
+      return;
+    }
+
+    jarNameToReferenceCountMap.put(jarName, 1);
+    jarNameToMd5Map.put(jarName, md5);
+  }
+
+  public synchronized void removeReference(final String jarName) {
+    final Integer referenceCount = jarNameToReferenceCountMap.get(jarName);
+    if (referenceCount == null || referenceCount <= 1) {
+      jarNameToReferenceCountMap.remove(jarName);
+      jarNameToMd5Map.remove(jarName);
+      return;
+    }
+
+    jarNameToReferenceCountMap.put(jarName, referenceCount - 1);
+  }
+
+  public synchronized void clear() {
+    jarNameToMd5Map.clear();
+    jarNameToReferenceCountMap.clear();
+  }
+
+  public synchronized Map<String, String> getJarNameToMd5Map() {
+    return new HashMap<>(jarNameToMd5Map);
+  }
+
+  public synchronized void serializeJarNameToMd5(final OutputStream 
outputStream)
+      throws IOException {
+    ReadWriteIOUtils.write(jarNameToMd5Map.size(), outputStream);
+    for (final Map.Entry<String, String> entry : jarNameToMd5Map.entrySet()) {
+      ReadWriteIOUtils.write(entry.getKey(), outputStream);
+      ReadWriteIOUtils.write(entry.getValue(), outputStream);
+    }
+  }
+
+  public synchronized void deserializeJarNameToMd5(final InputStream 
inputStream)
+      throws IOException {
+    clear();
+
+    int size = ReadWriteIOUtils.readInt(inputStream);
+    while (size > 0) {
+      addReference(
+          ReadWriteIOUtils.readString(inputStream), 
ReadWriteIOUtils.readString(inputStream));
+      size--;
+    }
+  }
+
+  public synchronized void serializeJarNameToMd5AndReferenceCount(final 
OutputStream outputStream)
+      throws IOException {
+    int size = 0;
+    for (final Map.Entry<String, Integer> entry : 
jarNameToReferenceCountMap.entrySet()) {
+      if (entry.getValue() > 0 && jarNameToMd5Map.containsKey(entry.getKey())) 
{
+        size++;
+      }
+    }
+
+    ReadWriteIOUtils.write(size, outputStream);
+    for (final Map.Entry<String, Integer> entry : 
jarNameToReferenceCountMap.entrySet()) {
+      final String jarName = entry.getKey();
+      final int referenceCount = entry.getValue();
+      if (referenceCount <= 0 || !jarNameToMd5Map.containsKey(jarName)) {
+        continue;
+      }
+      ReadWriteIOUtils.write(jarName, outputStream);
+      ReadWriteIOUtils.write(jarNameToMd5Map.get(jarName), outputStream);
+      ReadWriteIOUtils.write(referenceCount, outputStream);
+    }
+  }
+
+  public synchronized void deserializeJarNameToMd5AndReferenceCount(final 
InputStream inputStream)
+      throws IOException {
+    clear();
+
+    final int jarSize = ReadWriteIOUtils.readInt(inputStream);
+    for (int i = 0; i < jarSize; i++) {
+      final String jarName = ReadWriteIOUtils.readString(inputStream);
+      final String md5 = ReadWriteIOUtils.readString(inputStream);
+      final int referenceCount = ReadWriteIOUtils.readInt(inputStream);
+      if (referenceCount > 0) {
+        jarNameToMd5Map.put(jarName, md5);
+        jarNameToReferenceCountMap.put(jarName, referenceCount);
+      }
+    }
+  }
+}
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/plugin/meta/ConfigNodePipePluginMetaKeeper.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/plugin/meta/ConfigNodePipePluginMetaKeeper.java
index da4eb9cf714..2db7cf46135 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/plugin/meta/ConfigNodePipePluginMetaKeeper.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/plugin/meta/ConfigNodePipePluginMetaKeeper.java
@@ -19,80 +19,48 @@
 
 package org.apache.iotdb.commons.pipe.agent.plugin.meta;
 
-import org.apache.tsfile.utils.ReadWriteIOUtils;
+import org.apache.iotdb.commons.executable.ReferenceCountedJarMetaKeeper;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.util.HashMap;
-import java.util.Map;
 
 public class ConfigNodePipePluginMetaKeeper extends PipePluginMetaKeeper {
 
-  protected final Map<String, String> jarNameToMd5Map;
-  protected final Map<String, Integer> jarNameToReferenceCountMap;
+  protected final ReferenceCountedJarMetaKeeper jarMetaKeeper;
 
   public ConfigNodePipePluginMetaKeeper() {
     super();
 
-    jarNameToMd5Map = new HashMap<>();
-    jarNameToReferenceCountMap = new HashMap<>();
+    jarMetaKeeper = new ReferenceCountedJarMetaKeeper();
   }
 
   public synchronized boolean containsJar(String jarName) {
-    return jarNameToMd5Map.containsKey(jarName);
+    return jarMetaKeeper.containsJar(jarName);
   }
 
   public synchronized boolean jarNameExistsAndMatchesMd5(String jarName, 
String md5) {
-    return jarNameToMd5Map.containsKey(jarName) && 
jarNameToMd5Map.get(jarName).equals(md5);
+    return jarMetaKeeper.jarNameExistsAndMatchesMd5(jarName, md5);
   }
 
   public synchronized void addJarNameAndMd5(String jarName, String md5) {
-    if (jarNameToReferenceCountMap.containsKey(jarName)) {
-      jarNameToReferenceCountMap.put(jarName, 
jarNameToReferenceCountMap.get(jarName) + 1);
-    } else {
-      jarNameToReferenceCountMap.put(jarName, 1);
-      jarNameToMd5Map.put(jarName, md5);
-    }
+    jarMetaKeeper.addReference(jarName, md5);
   }
 
   public synchronized void removeJarNameAndMd5IfPossible(String jarName) {
-    if (jarNameToReferenceCountMap.containsKey(jarName)) {
-      int count = jarNameToReferenceCountMap.get(jarName);
-      if (count == 1) {
-        jarNameToReferenceCountMap.remove(jarName);
-        jarNameToMd5Map.remove(jarName);
-      } else {
-        jarNameToReferenceCountMap.put(jarName, count - 1);
-      }
-    }
+    jarMetaKeeper.removeReference(jarName);
   }
 
   @Override
   public void processTakeSnapshot(OutputStream outputStream) throws 
IOException {
-    ReadWriteIOUtils.write(jarNameToMd5Map.size(), outputStream);
-    for (Map.Entry<String, String> entry : jarNameToMd5Map.entrySet()) {
-      ReadWriteIOUtils.write(entry.getKey(), outputStream);
-      ReadWriteIOUtils.write(entry.getValue(), outputStream);
-      ReadWriteIOUtils.write(jarNameToReferenceCountMap.get(entry.getKey()), 
outputStream);
-    }
+    jarMetaKeeper.serializeJarNameToMd5AndReferenceCount(outputStream);
 
     super.processTakeSnapshot(outputStream);
   }
 
   @Override
   public void processLoadSnapshot(InputStream inputStream) throws IOException {
-    jarNameToMd5Map.clear();
-    jarNameToReferenceCountMap.clear();
-
-    final int jarSize = ReadWriteIOUtils.readInt(inputStream);
-    for (int i = 0; i < jarSize; i++) {
-      final String jarName = ReadWriteIOUtils.readString(inputStream);
-      final String md5 = ReadWriteIOUtils.readString(inputStream);
-      final int count = ReadWriteIOUtils.readInt(inputStream);
-      jarNameToMd5Map.put(jarName, md5);
-      jarNameToReferenceCountMap.put(jarName, count);
-    }
+    jarMetaKeeper.deserializeJarNameToMd5AndReferenceCount(inputStream);
 
     super.processLoadSnapshot(inputStream);
   }
diff --git 
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/executable/ReferenceCountedJarMetaKeeperTest.java
 
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/executable/ReferenceCountedJarMetaKeeperTest.java
new file mode 100644
index 00000000000..6a48701f574
--- /dev/null
+++ 
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/executable/ReferenceCountedJarMetaKeeperTest.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.iotdb.commons.executable;
+
+import org.apache.tsfile.utils.ReadWriteIOUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+public class ReferenceCountedJarMetaKeeperTest {
+
+  private static final String JAR_NAME = "shared.jar";
+  private static final String MD5 = "12345";
+
+  @Test
+  public void testReferenceCounting() {
+    final ReferenceCountedJarMetaKeeper keeper = new 
ReferenceCountedJarMetaKeeper();
+
+    keeper.addReference(JAR_NAME, MD5);
+    keeper.addReference(JAR_NAME, MD5);
+
+    Assert.assertTrue(keeper.containsJar(JAR_NAME));
+    Assert.assertFalse(keeper.needToSaveJar(JAR_NAME));
+    Assert.assertTrue(keeper.jarNameExistsAndMatchesMd5(JAR_NAME, MD5));
+    Assert.assertFalse(keeper.jarNameExistsAndMatchesMd5(JAR_NAME, "54321"));
+
+    keeper.removeReference(JAR_NAME);
+    Assert.assertTrue(keeper.containsJar(JAR_NAME));
+
+    keeper.removeReference(JAR_NAME);
+    Assert.assertFalse(keeper.containsJar(JAR_NAME));
+  }
+
+  @Test
+  public void testJarNameToMd5Snapshot() throws IOException {
+    final ReferenceCountedJarMetaKeeper keeper = new 
ReferenceCountedJarMetaKeeper();
+    keeper.addReference(JAR_NAME, MD5);
+    keeper.addReference(JAR_NAME, MD5);
+
+    final ReferenceCountedJarMetaKeeper loaded = new 
ReferenceCountedJarMetaKeeper();
+    loaded.deserializeJarNameToMd5(
+        new ByteArrayInputStream(serializeJarNameToMd5(keeper).toByteArray()));
+
+    Assert.assertTrue(loaded.jarNameExistsAndMatchesMd5(JAR_NAME, MD5));
+    loaded.removeReference(JAR_NAME);
+    Assert.assertFalse(loaded.containsJar(JAR_NAME));
+  }
+
+  @Test
+  public void testJarNameToMd5AndReferenceCountSnapshot() throws IOException {
+    final ReferenceCountedJarMetaKeeper keeper = new 
ReferenceCountedJarMetaKeeper();
+    keeper.addReference(JAR_NAME, MD5);
+    keeper.addReference(JAR_NAME, MD5);
+
+    final ReferenceCountedJarMetaKeeper loaded = new 
ReferenceCountedJarMetaKeeper();
+    loaded.deserializeJarNameToMd5AndReferenceCount(
+        new 
ByteArrayInputStream(serializeJarNameToMd5AndReferenceCount(keeper).toByteArray()));
+
+    Assert.assertTrue(loaded.jarNameExistsAndMatchesMd5(JAR_NAME, MD5));
+    loaded.removeReference(JAR_NAME);
+    Assert.assertTrue(loaded.containsJar(JAR_NAME));
+
+    loaded.removeReference(JAR_NAME);
+    Assert.assertFalse(loaded.containsJar(JAR_NAME));
+  }
+
+  @Test
+  public void 
testDeserializeJarNameToMd5AndReferenceCountSkipsZeroReferenceCount()
+      throws IOException {
+    final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+    ReadWriteIOUtils.write(1, outputStream);
+    ReadWriteIOUtils.write(JAR_NAME, outputStream);
+    ReadWriteIOUtils.write(MD5, outputStream);
+    ReadWriteIOUtils.write(0, outputStream);
+
+    final ReferenceCountedJarMetaKeeper loaded = new 
ReferenceCountedJarMetaKeeper();
+    loaded.deserializeJarNameToMd5AndReferenceCount(
+        new ByteArrayInputStream(outputStream.toByteArray()));
+
+    Assert.assertFalse(loaded.containsJar(JAR_NAME));
+  }
+
+  private ByteArrayOutputStream serializeJarNameToMd5(final 
ReferenceCountedJarMetaKeeper keeper)
+      throws IOException {
+    final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+    keeper.serializeJarNameToMd5(outputStream);
+    return outputStream;
+  }
+
+  private ByteArrayOutputStream serializeJarNameToMd5AndReferenceCount(
+      final ReferenceCountedJarMetaKeeper keeper) throws IOException {
+    final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+    keeper.serializeJarNameToMd5AndReferenceCount(outputStream);
+    return outputStream;
+  }
+}

Reply via email to