Skip to content

Commit 094b37a

Browse files
author
mhyeon-lee
committed
[DATACMNS-1735] Fix ConcurrencyModificationException with synchronized block
1 parent 4be5aae commit 094b37a

File tree

2 files changed

+46
-3
lines changed

2 files changed

+46
-3
lines changed

src/main/java/org/springframework/data/mapping/callback/EntityCallbackDiscoverer.java

+8-3
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
/**
4545
* @author Mark Paluch
4646
* @author Christoph Strobl
47+
* @author Myeonghyeon Lee
4748
* @since 2.2
4849
*/
4950
class EntityCallbackDiscoverer {
@@ -383,9 +384,13 @@ Collection<EntityCallback<?>> getEntityCallbacks() {
383384
if (this.entityCallbackBeans.isEmpty()) {
384385

385386
if (cachedEntityCallbacks.size() != entityCallbacks.size()) {
386-
cachedEntityCallbacks.clear();
387-
cachedEntityCallbacks.addAll(entityCallbacks);
388-
AnnotationAwareOrderComparator.sort(cachedEntityCallbacks);
387+
List<EntityCallback<?>> entityCallbacks = new ArrayList<>(this.entityCallbacks.size());
388+
AnnotationAwareOrderComparator.sort(entityCallbacks);
389+
390+
synchronized(this) {
391+
cachedEntityCallbacks.clear();
392+
cachedEntityCallbacks.addAll(entityCallbacks);
393+
}
389394
}
390395

391396
return cachedEntityCallbacks;

src/test/java/org/springframework/data/mapping/callback/EntityCallbackDiscovererUnitTests.java

+38
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
import static org.assertj.core.api.Assertions.*;
1919

2020
import java.util.Collection;
21+
import java.util.List;
22+
import java.util.concurrent.CopyOnWriteArrayList;
23+
import java.util.concurrent.CountDownLatch;
2124

2225
import org.junit.jupiter.api.Test;
2326
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@@ -33,6 +36,7 @@
3336

3437
/**
3538
* @author Christoph Strobl
39+
* @author Myeonghyeon Lee
3640
*/
3741
class EntityCallbackDiscovererUnitTests {
3842

@@ -49,6 +53,40 @@ void shouldDiscoverCallbackType() {
4953
assertThat(entityCallbacks).hasSize(1).element(0).isInstanceOf(MyBeforeSaveCallback.class);
5054
}
5155

56+
@Test // DATACMNS-1735
57+
void shouldDiscoverCallbackTypeConcurrencyCache() throws InterruptedException {
58+
59+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyConfig.class);
60+
61+
EntityCallbackDiscoverer discoverer = new EntityCallbackDiscoverer(ctx);
62+
63+
int concurrencyCount = 4000;
64+
CountDownLatch startLatch = new CountDownLatch(concurrencyCount);
65+
CountDownLatch doneLatch = new CountDownLatch(concurrencyCount);
66+
67+
List<Exception> exceptions = new CopyOnWriteArrayList<>();
68+
for (int i = 0; i < concurrencyCount; i++) {
69+
Thread thread = new Thread(() -> {
70+
try {
71+
startLatch.countDown();
72+
startLatch.await();
73+
74+
discoverer.getEntityCallbacks(PersonDocument.class,
75+
ResolvableType.forType(BeforeSaveCallback.class));
76+
} catch (Exception ex) {
77+
exceptions.add(ex);
78+
} finally {
79+
doneLatch.countDown();
80+
}
81+
});
82+
thread.start();
83+
}
84+
85+
doneLatch.await();
86+
87+
assertThat(exceptions).isEmpty();
88+
}
89+
5290
@Test // DATACMNS-1467
5391
void shouldDiscoverCallbackTypeByName() {
5492

0 commit comments

Comments
 (0)