|
17 | 17 |
|
18 | 18 | import static org.assertj.core.api.Assertions.assertThat;
|
19 | 19 | import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
| 20 | +import static org.assertj.core.api.Assumptions.assumeThat; |
20 | 21 |
|
| 22 | +import java.lang.reflect.Field; |
| 23 | +import java.util.ArrayList; |
| 24 | +import java.util.Collection; |
21 | 25 | import java.util.Collections;
|
| 26 | +import java.util.IdentityHashMap; |
| 27 | +import java.util.Map; |
| 28 | +import java.util.concurrent.Callable; |
| 29 | +import java.util.concurrent.ExecutionException; |
| 30 | +import java.util.concurrent.ExecutorService; |
| 31 | +import java.util.concurrent.Executors; |
| 32 | +import java.util.concurrent.Future; |
| 33 | +import java.util.concurrent.atomic.AtomicBoolean; |
| 34 | +import java.util.concurrent.atomic.AtomicInteger; |
22 | 35 |
|
23 | 36 | import org.junit.jupiter.api.Test;
|
24 | 37 | import org.junit.jupiter.params.ParameterizedTest;
|
|
31 | 44 | import org.springframework.data.neo4j.repository.query.Neo4jSpelSupport.LiteralReplacement;
|
32 | 45 | import org.springframework.data.repository.core.EntityMetadata;
|
33 | 46 | import org.springframework.expression.spel.standard.SpelExpressionParser;
|
| 47 | +import org.springframework.util.ReflectionUtils; |
34 | 48 |
|
35 | 49 | /**
|
36 | 50 | * @author Michael J. Simons
|
@@ -74,19 +88,79 @@ void orderByShouldWork() {
|
74 | 88 | .withMessageMatching(".+is not a valid order criteria.");
|
75 | 89 | }
|
76 | 90 |
|
| 91 | + private Map<?, ?> getCacheInstance() throws ClassNotFoundException, IllegalAccessException { |
| 92 | + Class<?> type = Class.forName( |
| 93 | + "org.springframework.data.neo4j.repository.query.Neo4jSpelSupport$StringBasedLiteralReplacement"); |
| 94 | + Field cacheField = ReflectionUtils.findField(type, "INSTANCES"); |
| 95 | + cacheField.setAccessible(true); |
| 96 | + return (Map<?, ?>) cacheField.get(null); |
| 97 | + } |
| 98 | + |
| 99 | + private void flushLiteralCache() { |
| 100 | + try { |
| 101 | + Map<?, ?> cache = getCacheInstance(); |
| 102 | + cache.clear(); |
| 103 | + } catch (Exception e) { |
| 104 | + throw new RuntimeException(e); |
| 105 | + } |
| 106 | + } |
| 107 | + |
| 108 | + private int getCacheSize() { |
| 109 | + try { |
| 110 | + Map<?, ?> cache = getCacheInstance(); |
| 111 | + return cache.size(); |
| 112 | + } catch (Exception e) { |
| 113 | + throw new RuntimeException(e); |
| 114 | + } |
| 115 | + } |
| 116 | + |
77 | 117 | @Test // DATAGRAPH-1454
|
78 | 118 | void cacheShouldWork() {
|
79 | 119 |
|
80 |
| - // Make sure we flush this before... |
81 |
| - for (int i = 0; i < 16; ++i) { |
82 |
| - LiteralReplacement literalReplacement = Neo4jSpelSupport.literal("y" + i); |
83 |
| - } |
| 120 | + flushLiteralCache(); |
84 | 121 |
|
85 | 122 | LiteralReplacement literalReplacement1 = Neo4jSpelSupport.literal("x");
|
86 | 123 | LiteralReplacement literalReplacement2 = Neo4jSpelSupport.literal("x");
|
87 | 124 | assertThat(literalReplacement1).isSameAs(literalReplacement2);
|
88 | 125 | }
|
89 | 126 |
|
| 127 | + @Test // GH-2375 |
| 128 | + void cacheShouldBeThreadSafe() throws ExecutionException, InterruptedException { |
| 129 | + |
| 130 | + flushLiteralCache(); |
| 131 | + |
| 132 | + int numThreads = Runtime.getRuntime().availableProcessors(); |
| 133 | + ExecutorService executor = Executors.newWorkStealingPool(); |
| 134 | + |
| 135 | + AtomicBoolean running = new AtomicBoolean(); |
| 136 | + AtomicInteger overlaps = new AtomicInteger(); |
| 137 | + |
| 138 | + Collection<Callable<LiteralReplacement>> getReplacementCalls = new ArrayList<>(); |
| 139 | + for (int t = 0; t < numThreads; ++t) { |
| 140 | + getReplacementCalls.add(() -> { |
| 141 | + if (!running.compareAndSet(false, true)) { |
| 142 | + overlaps.incrementAndGet(); |
| 143 | + } |
| 144 | + Thread.sleep(100); // Make the chances of overlapping a bit bigger |
| 145 | + LiteralReplacement d = Neo4jSpelSupport.literal("x"); |
| 146 | + running.compareAndSet(true, false); |
| 147 | + return d; |
| 148 | + }); |
| 149 | + } |
| 150 | + |
| 151 | + Map<LiteralReplacement, Integer> replacements = new IdentityHashMap<>(); |
| 152 | + for (Future<LiteralReplacement> getDriverFuture : executor.invokeAll(getReplacementCalls)) { |
| 153 | + replacements.put(getDriverFuture.get(), 1); |
| 154 | + } |
| 155 | + executor.shutdown(); |
| 156 | + |
| 157 | + // Assume things actually had been concurrent |
| 158 | + assumeThat(overlaps.get()).isGreaterThan(0); |
| 159 | + |
| 160 | + assertThat(getCacheSize()).isEqualTo(1); |
| 161 | + assertThat(replacements).hasSize(1); |
| 162 | + } |
| 163 | + |
90 | 164 | @ParameterizedTest // GH-2279
|
91 | 165 | @CsvSource({
|
92 | 166 | "MATCH (n:Something) WHERE n.name = ?#{#name}, MATCH (n:Something) WHERE n.name = ?__HASH__{#name}",
|
|
0 commit comments