Skip to content

Commit e6f588f

Browse files
authored
Removed concurrent hash map and replaced it with read/write lock to avoid concurrent mod. exception and recursive updates. (#10649)
1 parent 2332e64 commit e6f588f

File tree

3 files changed

+168
-10
lines changed

3 files changed

+168
-10
lines changed

builder/tests/common-types/src/main/java/io/helidon/common/types/TypeStash.java

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616

1717
package io.helidon.common.types;
1818

19+
import java.util.HashMap;
1920
import java.util.Map;
20-
import java.util.concurrent.ConcurrentHashMap;
21+
import java.util.concurrent.locks.ReadWriteLock;
22+
import java.util.concurrent.locks.ReentrantReadWriteLock;
2123

2224
/**
2325
* A cache of type names, to avoid duplication of non-generic instances in memory.
@@ -26,11 +28,14 @@ final class TypeStash {
2628
// in a simple registry example, we had over 820 calls to TypeName.create()
2729
// there were 183 records in this cache - this is a reasonable reduction in memory use
2830
// (though not as good as for type stash...)
29-
private static final Map<Class<?>, TypeName> CLASS_TYPE_STASH = new ConcurrentHashMap<>();
31+
private static final Map<Class<?>, TypeName> CLASS_TYPE_STASH = new HashMap<>();
32+
private static final ReadWriteLock CLASS_TYPE_STASH_LOCK = new ReentrantReadWriteLock();
33+
3034
// in a simple registry example, we had over 3000 calls to TypeName.create() without generics
3135
// there were 201 records in this cache - this is a very good reduction of used memory, as the types are quite often
3236
// stored for the runtime of the registry
33-
private static final Map<String, TypeName> TYPE_STASH = new ConcurrentHashMap<>();
37+
private static final Map<String, TypeName> TYPE_STASH = new HashMap<>();
38+
private static final ReadWriteLock TYPE_STASH_LOCK = new ReentrantReadWriteLock();
3439

3540
private TypeStash() {
3641
}
@@ -42,7 +47,31 @@ private TypeStash() {
4247
* @return type name either cached, or added to the cache
4348
*/
4449
static TypeName stash(Class<?> clazz) {
45-
return CLASS_TYPE_STASH.computeIfAbsent(clazz, TypeNameSupport::doCreate);
50+
// using rw lock to make sure we do not have a concurrent modification exception
51+
// the concurrent modification exception for example on first request, where we may do class initialization of
52+
// TypeNameSupport, which creates its own types
53+
CLASS_TYPE_STASH_LOCK.readLock().lock();
54+
TypeName typeName;
55+
try {
56+
typeName = CLASS_TYPE_STASH.get(clazz);
57+
if (typeName != null) {
58+
return typeName;
59+
}
60+
} finally {
61+
CLASS_TYPE_STASH_LOCK.readLock().unlock();
62+
}
63+
CLASS_TYPE_STASH_LOCK.writeLock().lock();
64+
try {
65+
typeName = CLASS_TYPE_STASH.get(clazz);
66+
if (typeName != null) {
67+
return typeName;
68+
}
69+
typeName = TypeNameSupport.doCreate(clazz);
70+
CLASS_TYPE_STASH.put(clazz, typeName);
71+
return typeName;
72+
} finally {
73+
CLASS_TYPE_STASH_LOCK.writeLock().unlock();
74+
}
4675
}
4776

4877
/**
@@ -57,6 +86,29 @@ static TypeName stash(String className) {
5786
// avoid generics
5887
return TypeNameSupport.doCreate(className);
5988
}
60-
return TYPE_STASH.computeIfAbsent(className, TypeNameSupport::doCreate);
89+
// using rw lock to make sure we do not have a concurrent modification exception
90+
// it may happen if we have typed argument that need a creation of another type name
91+
TYPE_STASH_LOCK.readLock().lock();
92+
TypeName typeName;
93+
try {
94+
typeName = TYPE_STASH.get(className);
95+
if (typeName != null) {
96+
return typeName;
97+
}
98+
} finally {
99+
TYPE_STASH_LOCK.readLock().unlock();
100+
}
101+
TYPE_STASH_LOCK.writeLock().lock();
102+
try {
103+
typeName = TYPE_STASH.get(className);
104+
if (typeName != null) {
105+
return typeName;
106+
}
107+
typeName = TypeNameSupport.doCreate(className);
108+
TYPE_STASH.put(className, typeName);
109+
return typeName;
110+
} finally {
111+
TYPE_STASH_LOCK.writeLock().unlock();
112+
}
61113
}
62114
}

common/types/src/main/java/io/helidon/common/types/TypeStash.java

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616

1717
package io.helidon.common.types;
1818

19+
import java.util.HashMap;
1920
import java.util.Map;
20-
import java.util.concurrent.ConcurrentHashMap;
21+
import java.util.concurrent.locks.ReadWriteLock;
22+
import java.util.concurrent.locks.ReentrantReadWriteLock;
2123

2224
/**
2325
* A cache of type names, to avoid duplication of non-generic instances in memory.
@@ -26,11 +28,14 @@ final class TypeStash {
2628
// in a simple registry example, we had over 820 calls to TypeName.create()
2729
// there were 183 records in this cache - this is a reasonable reduction in memory use
2830
// (though not as good as for type stash...)
29-
private static final Map<Class<?>, TypeName> CLASS_TYPE_STASH = new ConcurrentHashMap<>();
31+
private static final Map<Class<?>, TypeName> CLASS_TYPE_STASH = new HashMap<>();
32+
private static final ReadWriteLock CLASS_TYPE_STASH_LOCK = new ReentrantReadWriteLock();
33+
3034
// in a simple registry example, we had over 3000 calls to TypeName.create() without generics
3135
// there were 201 records in this cache - this is a very good reduction of used memory, as the types are quite often
3236
// stored for the runtime of the registry
33-
private static final Map<String, TypeName> TYPE_STASH = new ConcurrentHashMap<>();
37+
private static final Map<String, TypeName> TYPE_STASH = new HashMap<>();
38+
private static final ReadWriteLock TYPE_STASH_LOCK = new ReentrantReadWriteLock();
3439

3540
private TypeStash() {
3641
}
@@ -42,7 +47,31 @@ private TypeStash() {
4247
* @return type name either cached, or added to the cache
4348
*/
4449
static TypeName stash(Class<?> clazz) {
45-
return CLASS_TYPE_STASH.computeIfAbsent(clazz, TypeNameSupport::doCreate);
50+
// using rw lock to make sure we do not have a concurrent modification exception
51+
// the concurrent modification exception for example on first request, where we may do class initialization of
52+
// TypeNameSupport, which creates its own types
53+
CLASS_TYPE_STASH_LOCK.readLock().lock();
54+
TypeName typeName;
55+
try {
56+
typeName = CLASS_TYPE_STASH.get(clazz);
57+
if (typeName != null) {
58+
return typeName;
59+
}
60+
} finally {
61+
CLASS_TYPE_STASH_LOCK.readLock().unlock();
62+
}
63+
CLASS_TYPE_STASH_LOCK.writeLock().lock();
64+
try {
65+
typeName = CLASS_TYPE_STASH.get(clazz);
66+
if (typeName != null) {
67+
return typeName;
68+
}
69+
typeName = TypeNameSupport.doCreate(clazz);
70+
CLASS_TYPE_STASH.put(clazz, typeName);
71+
return typeName;
72+
} finally {
73+
CLASS_TYPE_STASH_LOCK.writeLock().unlock();
74+
}
4675
}
4776

4877
/**
@@ -57,6 +86,29 @@ static TypeName stash(String className) {
5786
// avoid generics
5887
return TypeNameSupport.doCreate(className);
5988
}
60-
return TYPE_STASH.computeIfAbsent(className, TypeNameSupport::doCreate);
89+
// using rw lock to make sure we do not have a concurrent modification exception
90+
// in case this is called first (again possible conflict with class initialization)
91+
TYPE_STASH_LOCK.readLock().lock();
92+
TypeName typeName;
93+
try {
94+
typeName = TYPE_STASH.get(className);
95+
if (typeName != null) {
96+
return typeName;
97+
}
98+
} finally {
99+
TYPE_STASH_LOCK.readLock().unlock();
100+
}
101+
TYPE_STASH_LOCK.writeLock().lock();
102+
try {
103+
typeName = TYPE_STASH.get(className);
104+
if (typeName != null) {
105+
return typeName;
106+
}
107+
typeName = TypeNameSupport.doCreate(className);
108+
TYPE_STASH.put(className, typeName);
109+
return typeName;
110+
} finally {
111+
TYPE_STASH_LOCK.writeLock().unlock();
112+
}
61113
}
62114
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright (c) 2025 Oracle and/or its affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.helidon.common.types;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import static org.hamcrest.CoreMatchers.is;
22+
import static org.hamcrest.CoreMatchers.sameInstance;
23+
import static org.hamcrest.MatcherAssert.assertThat;
24+
25+
class TypeStashTest {
26+
27+
@Test
28+
void ensureSameInstance() {
29+
TypeName first = TypeStash.stash(TypeStashTest.class);
30+
TypeName second = TypeStash.stash(TypeStashTest.class);
31+
32+
assertThat(first, is(sameInstance(second)));
33+
}
34+
35+
@Test
36+
void ensureSameInstanceString() {
37+
String type = "java.util.List";
38+
39+
TypeName first = TypeStash.stash(type);
40+
TypeName second = TypeStash.stash(type);
41+
42+
assertThat(first, is(sameInstance(second)));
43+
}
44+
45+
@Test
46+
void ensureEqualInstanceGenerics() {
47+
String type = "java.util.List<java.lang.String>";
48+
49+
TypeName first = TypeStash.stash(type);
50+
TypeName second = TypeStash.stash(type);
51+
52+
assertThat(first, is(second));
53+
}
54+
}

0 commit comments

Comments
 (0)