|
32 | 32 | import java.util.HashMap; |
33 | 33 | import java.util.Map; |
34 | 34 | import java.util.Optional; |
| 35 | +import java.util.concurrent.CompletableFuture; |
| 36 | +import java.util.concurrent.CountDownLatch; |
| 37 | +import java.util.concurrent.ExecutorService; |
| 38 | +import java.util.concurrent.Executors; |
| 39 | +import java.util.concurrent.TimeUnit; |
| 40 | +import java.util.stream.IntStream; |
35 | 41 |
|
36 | 42 | import org.assertj.core.api.AbstractLongAssert; |
37 | 43 | import org.junit.jupiter.api.BeforeEach; |
@@ -275,6 +281,61 @@ void shouldPassthruTemporalValue() { |
275 | 281 | assertThat(source.created).isEqualTo(now); |
276 | 282 | } |
277 | 283 |
|
| 284 | + @Test // GH-3441 |
| 285 | + void getBeanWrapperForIsThreadSafe() throws Exception { |
| 286 | + |
| 287 | + int threadCount = 50; |
| 288 | + int iterationsPerThread = 100; |
| 289 | + ExecutorService executor = Executors.newFixedThreadPool(threadCount); |
| 290 | + CountDownLatch startLatch = new CountDownLatch(1); |
| 291 | + CountDownLatch completionLatch = new CountDownLatch(threadCount); |
| 292 | + |
| 293 | + try { |
| 294 | + |
| 295 | + var futures = IntStream.range(0, threadCount).mapToObj(threadIndex -> CompletableFuture.runAsync(() -> { |
| 296 | + try { |
| 297 | + startLatch.await(); |
| 298 | + |
| 299 | + for (int i = 0; i < iterationsPerThread; i++) { |
| 300 | + var sample = new Sample(); |
| 301 | + var wrapper = factory.getBeanWrapperFor(sample); |
| 302 | + |
| 303 | + assertThat(wrapper).isPresent(); |
| 304 | + assertThat(wrapper.get().getBean()).isSameAs(sample); |
| 305 | + |
| 306 | + var sampleWithInstant = new SampleWithInstant(); |
| 307 | + var wrapperWithInstant = factory.getBeanWrapperFor(sampleWithInstant); |
| 308 | + |
| 309 | + assertThat(wrapperWithInstant).isPresent(); |
| 310 | + assertThat(wrapperWithInstant.get().getBean()).isSameAs(sampleWithInstant); |
| 311 | + |
| 312 | + var withEmbedded = new WithEmbedded(); |
| 313 | + var wrapperWithEmbedded = factory.getBeanWrapperFor(withEmbedded); |
| 314 | + |
| 315 | + assertThat(wrapperWithEmbedded).isPresent(); |
| 316 | + assertThat(wrapperWithEmbedded.get().getBean()).isSameAs(withEmbedded); |
| 317 | + } |
| 318 | + } catch (InterruptedException e) { |
| 319 | + Thread.currentThread().interrupt(); |
| 320 | + throw new RuntimeException(e); |
| 321 | + } finally { |
| 322 | + completionLatch.countDown(); |
| 323 | + } |
| 324 | + }, executor)).toList(); |
| 325 | + |
| 326 | + startLatch.countDown(); |
| 327 | + |
| 328 | + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).get(30, TimeUnit.SECONDS); |
| 329 | + assertThat(completionLatch.await(1, TimeUnit.SECONDS)).isTrue(); |
| 330 | + |
| 331 | + Map<?, ?> metadataCache = (Map<?, ?>) ReflectionTestUtils.getField(factory, "metadataCache"); |
| 332 | + assertThat(metadataCache).hasSize(4); |
| 333 | + } finally { |
| 334 | + executor.shutdown(); |
| 335 | + assertThat(executor.awaitTermination(5, TimeUnit.SECONDS)).isTrue(); |
| 336 | + } |
| 337 | + } |
| 338 | + |
278 | 339 | private void assertLastModificationDate(Object source, TemporalAccessor expected) { |
279 | 340 |
|
280 | 341 | var sample = new Sample(); |
|
0 commit comments