DATACMNS-1210 - Fix concurrency issue in BasicPersitentEntity.

Make sure to use concurrent collection implementations for properties holding mutable state. Especially the ones making use of computeIfAbsent(…).

Usage of ConcurrentReferenceHashMap allows us to move on without additional changes as it allows to store null values, whereas ConcurrentHashMap would require us to store explicit NullValue placeholders for such cases, potentially causing trouble in downstream store specific projects.

JMH Benchmarks done with the Spring Data MongoDB mapping layer showed a potential performance loss of up to 5%. However a comparison between ConcurrentReferenceHashMap and ConcurrentHashMap did not show any significant difference in performance.

Original pull request: #259.
This commit is contained in:
Christoph Strobl
2017-11-14 07:52:36 +01:00
committed by Oliver Gierke
parent d8cea8ba1c
commit 309cdc5e43
2 changed files with 88 additions and 8 deletions

View File

@@ -19,11 +19,14 @@ import static org.assertj.core.api.Assertions.*;
import static org.junit.Assume.*;
import static org.mockito.Mockito.*;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import org.hamcrest.CoreMatchers;
import org.junit.Rule;
@@ -34,9 +37,12 @@ import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.core.annotation.AliasFor;
import org.springframework.data.annotation.AccessType;
import org.springframework.data.annotation.AccessType.Type;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.Persistent;
import org.springframework.data.annotation.TypeAlias;
import org.springframework.data.mapping.Alias;
import org.springframework.data.mapping.Document;
@@ -49,6 +55,7 @@ import org.springframework.data.mapping.Person;
import org.springframework.data.mapping.context.SampleMappingContext;
import org.springframework.data.mapping.context.SamplePersistentProperty;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.Version;
import org.springframework.test.util.ReflectionTestUtils;
/**
@@ -276,6 +283,64 @@ public class BasicPersistentEntityUnitTests<T extends PersistentProperty<T>> {
assertThatThrownBy(() -> entity.getRequiredAnnotation(Document.class)).isInstanceOf(IllegalStateException.class);
}
@Test // DATACMNS-1210
public void findAnnotationShouldBeThreadSafe() throws InterruptedException {
assumeTrue("Requires Java 9",
Version.parse(System.getProperty("java.version")).isGreaterThanOrEqualTo(Version.parse("9.0")));
CountDownLatch latch = new CountDownLatch(2);
CountDownLatch syncLatch = new CountDownLatch(1);
final AtomicBoolean failed = new AtomicBoolean(false);
PersistentEntity<EntityWithAnnotation, T> entity = new BasicPersistentEntity(
ClassTypeInformation.from(EntityWithAnnotation.class), null) {
@Override
public Annotation findAnnotation(Class annotationType) {
try {
syncLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
return super.findAnnotation(annotationType);
}
};
Runnable findAccessType = () -> {
try {
entity.findAnnotation(AccessType.class);
} catch (Exception e) {
failed.set(true);
} finally {
latch.countDown();
}
};
Runnable findPersistent = () -> {
try {
entity.findAnnotation(Persistent.class);
} catch (Exception e) {
failed.set(true);
} finally {
latch.countDown();
}
};
new Thread(findAccessType).start();
new Thread(findPersistent).start();
syncLatch.countDown();
latch.await();
assertThat(failed.get()).isFalse();
}
private <S> BasicPersistentEntity<S, T> createEntity(Class<S> type) {
return createEntity(type, null);
}
@@ -315,4 +380,9 @@ public class BasicPersistentEntityUnitTests<T extends PersistentProperty<T>> {
static class AliasEntityUsingComposedAnnotation {}
static class Subtype extends Entity {}
@AccessType(Type.PROPERTY)
static class EntityWithAnnotation {
}
}