BeanDescriptionConcurrent6227Test.java
package tools.jackson.databind.introspect;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.*;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonKey;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonValue;
import tools.jackson.databind.BeanDescription;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.ObjectMapperTestAccess;
import tools.jackson.databind.testutil.DatabindTestUtil;
import static org.junit.jupiter.api.Assertions.assertTrue;
// [databind#6227]
public class BeanDescriptionConcurrent6227Test extends DatabindTestUtil
{
static class Probe {
public final String id;
public final int count;
@JsonCreator
public Probe(@JsonProperty("id") String id, @JsonProperty("count") int count) {
this.id = id;
this.count = count;
}
}
// Both field and getter annotated: resolution modifies accessor list
static class ValueProbe {
@JsonValue
public String value = "x";
@JsonValue
public String getValue() { return value; }
}
static class KeyProbe {
@JsonKey
public String key = "x";
@JsonKey
public String getKey() { return key; }
}
@FunctionalInterface
interface DescAction {
void run(BeanDescription desc) throws Exception;
}
@Test
public void testConcurrentFindProperties() throws Exception
{
ObjectMapper mapper = newJsonMapper();
// Repeat a number of times to increase odds of hitting race
for (int round = 0; round < 50; ++round) {
final BeanDescription shared = ObjectMapperTestAccess.beanDescriptionForSer(mapper, Probe.class);
_runRound(shared, 16, BeanDescription::findProperties);
}
}
@Test
public void testConcurrentFindJsonValueAccessor() throws Exception
{
ObjectMapper mapper = newJsonMapper();
for (int round = 0; round < 50; ++round) {
final BeanDescription shared = ObjectMapperTestAccess.beanDescriptionForSer(mapper, ValueProbe.class);
// Getter has precedence over field
_runRound(shared, 16, desc -> assertTrue(
desc.findJsonValueAccessor() instanceof AnnotatedMethod));
}
}
@Test
public void testConcurrentFindJsonKeyAccessor() throws Exception
{
ObjectMapper mapper = newJsonMapper();
for (int round = 0; round < 50; ++round) {
final BeanDescription shared = ObjectMapperTestAccess.beanDescriptionForSer(mapper, KeyProbe.class);
// Getter has precedence over field
_runRound(shared, 16, desc -> assertTrue(
desc.findJsonKeyAccessor() instanceof AnnotatedMethod));
}
}
private void _runRound(final BeanDescription shared, int parallelism,
final DescAction action) throws Exception
{
final CyclicBarrier barrier = new CyclicBarrier(parallelism);
final Queue<Throwable> errors = new ConcurrentLinkedQueue<>();
ExecutorService pool = Executors.newFixedThreadPool(parallelism);
List<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < parallelism; i++) {
futures.add(pool.submit(() -> {
try {
barrier.await();
action.run(shared);
} catch (Throwable e) {
errors.add(e);
}
}));
}
for (Future<?> f : futures) {
f.get(30, TimeUnit.SECONDS);
}
pool.shutdown();
if (!errors.isEmpty()) {
throw new AssertionError("Failed with "+errors.size()+" errors, first: "+errors.peek(), errors.peek());
}
assertTrue(errors.isEmpty());
}
}