You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by al...@apache.org on 2021/11/01 11:00:38 UTC

[dubbo] branch 3.0 updated: Fix MD5Utils concurrent error (#9176)

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

albumenj pushed a commit to branch 3.0
in repository https://gitbox.apache.org/repos/asf/dubbo.git


The following commit(s) were added to refs/heads/3.0 by this push:
     new 68381ba  Fix MD5Utils concurrent error (#9176)
68381ba is described below

commit 68381ba7662fb65d68a38a7f966123714808f110
Author: Gong Dewei <ky...@qq.com>
AuthorDate: Mon Nov 1 19:00:06 2021 +0800

    Fix MD5Utils concurrent error (#9176)
---
 .../org/apache/dubbo/common/utils/MD5Utils.java    | 19 +++--
 .../apache/dubbo/common/utils/MD5UtilsTest.java    | 95 ++++++++++++++++++++++
 .../support/nacos/NacosDynamicConfiguration.java   |  4 +-
 .../apache/dubbo/metadata/RevisionResolver.java    |  4 +-
 .../metadata/store/nacos/NacosMetadataReport.java  | 14 ++--
 5 files changed, 121 insertions(+), 15 deletions(-)

diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/MD5Utils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/MD5Utils.java
index 92b520d..2d48631 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/MD5Utils.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/MD5Utils.java
@@ -36,9 +36,9 @@ public class MD5Utils {
             '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
     };
 
-    private static MessageDigest mdInst;
+    private MessageDigest mdInst;
 
-    static {
+    public MD5Utils() {
         try {
             mdInst = MessageDigest.getInstance("MD5");
         } catch (NoSuchAlgorithmException e) {
@@ -46,9 +46,17 @@ public class MD5Utils {
         }
     }
 
-    public static String getMd5(String value) {
-        mdInst.update(value.getBytes(UTF_8));
-        byte[] md5 = mdInst.digest();
+    /**
+     * Calculation md5 value of specify string
+     * @param input
+     */
+    public String getMd5(String input) {
+        byte[] md5;
+        // MessageDigest instance is NOT thread-safe
+        synchronized (mdInst) {
+            mdInst.update(input.getBytes(UTF_8));
+            md5 = mdInst.digest();
+        }
 
         int j = md5.length;
         char str[] = new char[j * 2];
@@ -61,5 +69,4 @@ public class MD5Utils {
         return new String(str);
     }
 
-
 }
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/MD5UtilsTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/MD5UtilsTest.java
new file mode 100644
index 0000000..4a5ca05
--- /dev/null
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/MD5UtilsTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.dubbo.common.utils;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class MD5UtilsTest {
+
+    @Test
+    public void test() {
+        MD5Utils sharedMd5Utils = new MD5Utils();
+        final String[] input = {"provider-appgroup-one/org.apache.dubbo.config.spring.api.HelloService:dubboorg.apache.dubbo.config.spring.api.HelloService{REGISTRY_CLUSTER=registry-one, anyhost=true, application=provider-app, background=false, compiler=javassist, deprecated=false, dubbo=2.0.2, dynamic=true, file.cache=false, generic=false, group=group-one, interface=org.apache.dubbo.config.spring.api.HelloService, logger=slf4j, metadata-type=remote, methods=sayHello, organization=test,  [...]
+            "provider-appgroup-two/org.apache.dubbo.config.spring.api.DemoService:dubboorg.apache.dubbo.config.spring.api.DemoService{REGISTRY_CLUSTER=registry-two, anyhost=true, application=provider-app, background=false, compiler=javassist, deprecated=false, dubbo=2.0.2, dynamic=true, file.cache=false, generic=false, group=group-two, interface=org.apache.dubbo.config.spring.api.DemoService, logger=slf4j, metadata-type=remote, methods=sayName,getBox, organization=test, owner=com.test, r [...]
+        final String[] result = {sharedMd5Utils.getMd5(input[0]), new MD5Utils().getMd5(input[1])};
+
+        System.out.println("Expected result: " + Arrays.asList(result));
+        int nThreads = 8;
+        CountDownLatch latch = new CountDownLatch(nThreads);
+        List<Throwable> errors = Collections.synchronizedList(new ArrayList<>());
+        ExecutorService executorService = Executors.newFixedThreadPool(nThreads);
+        try {
+
+            for (int i = 0; i < nThreads; i++) {
+                MD5Utils md5Utils = i < nThreads / 2 ? sharedMd5Utils : new MD5Utils();
+                executorService.submit(new Md5Task(input[i % 2], result[i % 2], md5Utils, latch, errors));
+            }
+            latch.await();
+            Assertions.assertEquals(Collections.EMPTY_LIST, errors);
+            Assertions.assertEquals(0, latch.getCount());
+        } catch (Throwable e) {
+            Assertions.fail(StringUtils.toString(e));
+        } finally {
+            executorService.shutdown();
+        }
+    }
+
+    static class Md5Task implements Runnable {
+
+        private final String input;
+        private final String expected;
+        private final MD5Utils md5Utils;
+        private final CountDownLatch latch;
+        private final List<Throwable> errorCollector;
+
+        public Md5Task(String input, String expected, MD5Utils md5Utils, CountDownLatch latch, List<Throwable> errorCollector) {
+            this.input = input;
+            this.expected = expected;
+            this.md5Utils = md5Utils;
+            this.latch = latch;
+            this.errorCollector = errorCollector;
+        }
+
+        @Override
+        public void run() {
+            int i = 0;
+            long start = System.currentTimeMillis();
+            try {
+                for (; i < 200; i++) {
+                    Assertions.assertEquals(expected, md5Utils.getMd5(input));
+                    md5Utils.getMd5("test#" + i);
+                }
+            } catch (Throwable e) {
+                errorCollector.add(e);
+                e.printStackTrace();
+            } finally {
+                long cost = System.currentTimeMillis() - start;
+                System.out.println("[" + Thread.currentThread().getName() + "] progress: " + i + ", cost: " + cost);
+                latch.countDown();
+            }
+        }
+    }
+}
diff --git a/dubbo-configcenter/dubbo-configcenter-nacos/src/main/java/org/apache/dubbo/configcenter/support/nacos/NacosDynamicConfiguration.java b/dubbo-configcenter/dubbo-configcenter-nacos/src/main/java/org/apache/dubbo/configcenter/support/nacos/NacosDynamicConfiguration.java
index 83a0fac..7d35543 100644
--- a/dubbo-configcenter/dubbo-configcenter-nacos/src/main/java/org/apache/dubbo/configcenter/support/nacos/NacosDynamicConfiguration.java
+++ b/dubbo-configcenter/dubbo-configcenter-nacos/src/main/java/org/apache/dubbo/configcenter/support/nacos/NacosDynamicConfiguration.java
@@ -85,6 +85,8 @@ public class NacosDynamicConfiguration implements DynamicConfiguration {
      */
     private final Map<String, NacosConfigListener> watchListenerMap;
 
+    private MD5Utils md5Utils = new MD5Utils();
+
     NacosDynamicConfiguration(URL url) {
         this.nacosProperties = buildNacosProperties(url);
         this.configService = buildConfigService(url);
@@ -221,7 +223,7 @@ public class NacosDynamicConfiguration implements DynamicConfiguration {
         String content = getConfig(key, group);
         String casMd5 = "";
         if (StringUtils.isNotEmpty(content)) {
-            casMd5 = MD5Utils.getMd5(content);
+            casMd5 = md5Utils.getMd5(content);
         }
         return new ConfigItem(content, casMd5);
     }
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/RevisionResolver.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/RevisionResolver.java
index 155295c..a1c9e19 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/RevisionResolver.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/RevisionResolver.java
@@ -23,8 +23,10 @@ public class RevisionResolver {
 
     public static final String EMPTY_REVISION = "0";
 
+    private static MD5Utils md5Utils = new MD5Utils();
+
     public static String calRevision(String metadata) {
-        return MD5Utils.getMd5(metadata);
+        return md5Utils.getMd5(metadata);
     }
 
 }
diff --git a/dubbo-metadata/dubbo-metadata-report-nacos/src/main/java/org/apache/dubbo/metadata/store/nacos/NacosMetadataReport.java b/dubbo-metadata/dubbo-metadata-report-nacos/src/main/java/org/apache/dubbo/metadata/store/nacos/NacosMetadataReport.java
index 30b26b7..23e866a 100644
--- a/dubbo-metadata/dubbo-metadata-report-nacos/src/main/java/org/apache/dubbo/metadata/store/nacos/NacosMetadataReport.java
+++ b/dubbo-metadata/dubbo-metadata-report-nacos/src/main/java/org/apache/dubbo/metadata/store/nacos/NacosMetadataReport.java
@@ -17,6 +17,11 @@
 
 package org.apache.dubbo.metadata.store.nacos;
 
+import com.alibaba.nacos.api.NacosFactory;
+import com.alibaba.nacos.api.PropertyKeyConst;
+import com.alibaba.nacos.api.config.listener.AbstractSharedListener;
+import com.alibaba.nacos.api.exception.NacosException;
+import com.google.gson.Gson;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.config.configcenter.ConfigChangeType;
 import org.apache.dubbo.common.config.configcenter.ConfigChangedEvent;
@@ -36,12 +41,6 @@ import org.apache.dubbo.metadata.report.identifier.SubscriberMetadataIdentifier;
 import org.apache.dubbo.metadata.report.support.AbstractMetadataReport;
 import org.apache.dubbo.rpc.RpcException;
 
-import com.alibaba.nacos.api.NacosFactory;
-import com.alibaba.nacos.api.PropertyKeyConst;
-import com.alibaba.nacos.api.config.listener.AbstractSharedListener;
-import com.alibaba.nacos.api.exception.NacosException;
-import com.google.gson.Gson;
-
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -80,6 +79,7 @@ public class NacosMetadataReport extends AbstractMetadataReport {
 
     private Map<String, MappingDataListener> casListenerMap = new ConcurrentHashMap<>();
 
+    private MD5Utils md5Utils = new MD5Utils();
 
     public NacosMetadataReport(URL url) {
         super(url);
@@ -226,7 +226,7 @@ public class NacosMetadataReport extends AbstractMetadataReport {
         String content = getConfig(key, group);
         String casMd5 = "";
         if (StringUtils.isNotEmpty(content)) {
-            casMd5 = MD5Utils.getMd5(content);
+            casMd5 = md5Utils.getMd5(content);
         }
         return new ConfigItem(content, casMd5);
     }