Fix MD5Utils concurrent error (#9176)

This commit is contained in:
Gong Dewei 2021-11-01 19:00:06 +08:00 committed by GitHub
parent ef2c0b3fcc
commit 68381ba766
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 121 additions and 15 deletions

View File

@ -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);
}
}

View File

@ -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, owner=com.test, release=, service-name-mapping=true, side=provider}",
"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, release=, service-name-mapping=true, side=provider}"};
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();
}
}
}
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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);
}