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