Skip to content

Commit f992a6a

Browse files
committed
fix: use ConcurrentHashMap for Version.VERSION2INT to prevent concurrent corruption
VERSION2INT uses a plain HashMap that is read and written concurrently from the RPC hot path (isSupportResponseAttachment is checked on every call). Concurrent put() during HashMap resize can corrupt the internal hash table, causing threads to hang in infinite loops.
1 parent 63fe8c3 commit f992a6a

2 files changed

Lines changed: 40 additions & 2 deletions

File tree

dubbo-common/src/main/java/org/apache/dubbo/common/Version.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@
2828
import java.nio.charset.StandardCharsets;
2929
import java.security.CodeSource;
3030
import java.util.Enumeration;
31-
import java.util.HashMap;
3231
import java.util.HashSet;
3332
import java.util.Map;
3433
import java.util.Set;
34+
import java.util.concurrent.ConcurrentHashMap;
3535
import java.util.regex.Matcher;
3636
import java.util.regex.Pattern;
3737

@@ -58,7 +58,7 @@ public final class Version {
5858
public static final int LOWEST_VERSION_FOR_RESPONSE_ATTACHMENT = 2000200; // 2.0.2
5959

6060
public static final int HIGHEST_PROTOCOL_VERSION = 2009900; // 2.0.99
61-
private static final Map<String, Integer> VERSION2INT = new HashMap<>();
61+
private static final Map<String, Integer> VERSION2INT = new ConcurrentHashMap<>();
6262

6363
static {
6464
// get dubbo version and last commit id

dubbo-common/src/test/java/org/apache/dubbo/common/version/VersionTest.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import java.lang.reflect.InvocationTargetException;
2525
import java.net.URL;
2626
import java.util.Enumeration;
27+
import java.util.concurrent.CountDownLatch;
28+
import java.util.concurrent.atomic.AtomicInteger;
2729

2830
import org.junit.jupiter.api.Assertions;
2931
import org.junit.jupiter.api.Test;
@@ -98,6 +100,42 @@ void testIsFramework263OrHigher() {
98100
Assertions.assertTrue(Version.isRelease263OrHigher("2.6.3.0"));
99101
}
100102

103+
@Test
104+
void testGetIntVersionConcurrency() throws InterruptedException {
105+
int threadCount = 10;
106+
int iterations = 1000;
107+
CountDownLatch startLatch = new CountDownLatch(1);
108+
CountDownLatch doneLatch = new CountDownLatch(threadCount);
109+
AtomicInteger errors = new AtomicInteger(0);
110+
111+
String[] versions = {"2.6.1", "2.7.0", "3.0.0", "2.0.2", "2.0.99", "3.1.0", "2.6.3.1", "2.7.0.RC1"};
112+
int[] expected = {2060100, 2070000, 3000000, 2000200, 2009900, 3010000, 2060301, 2070000};
113+
114+
for (int t = 0; t < threadCount; t++) {
115+
new Thread(() -> {
116+
try {
117+
startLatch.await();
118+
for (int i = 0; i < iterations; i++) {
119+
int idx = i % versions.length;
120+
int result = Version.getIntVersion(versions[idx]);
121+
if (result != expected[idx]) {
122+
errors.incrementAndGet();
123+
}
124+
}
125+
} catch (Exception e) {
126+
errors.incrementAndGet();
127+
} finally {
128+
doneLatch.countDown();
129+
}
130+
})
131+
.start();
132+
}
133+
134+
startLatch.countDown();
135+
doneLatch.await();
136+
Assertions.assertEquals(0, errors.get(), "Concurrent getIntVersion should return consistent results");
137+
}
138+
101139
@Test
102140
void testGetVersion()
103141
throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {

0 commit comments

Comments
 (0)