|
16 | 16 |
|
17 | 17 | package org.springframework.data.redis.cache;
|
18 | 18 |
|
| 19 | +import static org.hamcrest.core.Is.is; |
19 | 20 | import static org.hamcrest.core.IsEqual.*;
|
20 | 21 | import static org.hamcrest.core.IsInstanceOf.*;
|
21 | 22 | import static org.hamcrest.core.IsNot.*;
|
|
26 | 27 | import static org.springframework.data.redis.matcher.RedisTestMatchers.*;
|
27 | 28 |
|
28 | 29 | import java.util.Collection;
|
29 |
| -import java.util.List; |
30 | 30 | import java.util.concurrent.Callable;
|
31 |
| -import java.util.concurrent.CopyOnWriteArrayList; |
32 | 31 | import java.util.concurrent.CountDownLatch;
|
33 | 32 | import java.util.concurrent.TimeUnit;
|
34 | 33 | import java.util.concurrent.atomic.AtomicBoolean;
|
35 |
| -import java.util.concurrent.atomic.AtomicLong; |
36 | 34 |
|
| 35 | +import edu.umd.cs.mtc.MultithreadedTestCase; |
| 36 | +import edu.umd.cs.mtc.TestFramework; |
37 | 37 | import org.hamcrest.core.IsEqual;
|
38 | 38 | import org.junit.AfterClass;
|
39 | 39 | import org.junit.Before;
|
|
44 | 44 | import org.springframework.cache.Cache;
|
45 | 45 | import org.springframework.cache.Cache.ValueWrapper;
|
46 | 46 | import org.springframework.data.redis.ConnectionFactoryTracker;
|
47 |
| -import org.springframework.data.redis.LongObjectFactory; |
48 | 47 | import org.springframework.data.redis.ObjectFactory;
|
| 48 | +import org.springframework.data.redis.StringObjectFactory; |
49 | 49 | import org.springframework.data.redis.core.AbstractOperationsTestParams;
|
50 | 50 | import org.springframework.data.redis.core.RedisTemplate;
|
51 | 51 |
|
52 | 52 | /**
|
53 | 53 | * @author Costin Leau
|
54 | 54 | * @author Jennifer Hickey
|
55 | 55 | * @author Christoph Strobl
|
| 56 | + * @author Mark Paluch |
56 | 57 | */
|
57 | 58 | @SuppressWarnings("rawtypes")
|
58 | 59 | @RunWith(Parameterized.class)
|
@@ -283,50 +284,66 @@ public void putIfAbsentShouldSetValueOnlyIfNotPresent() {
|
283 | 284 |
|
284 | 285 | /**
|
285 | 286 | * @see DATAREDIS-443
|
| 287 | + * @see DATAREDIS-452 |
286 | 288 | */
|
287 | 289 | @Test
|
288 |
| -public void testCacheGetSynchronized() throws InterruptedException { |
| 290 | +public void testCacheGetSynchronized() throws Throwable { |
289 | 291 |
|
290 | 292 | assumeThat(cache, instanceOf(RedisCache.class));
|
291 |
| -assumeThat(valueFactory, instanceOf(LongObjectFactory.class)); |
| 293 | +assumeThat(valueFactory, instanceOf(StringObjectFactory.class)); |
292 | 294 |
|
293 |
| -int threadCount = 10; |
294 |
| -final AtomicLong counter = new AtomicLong(); |
295 |
| -final List<Object> results = new CopyOnWriteArrayList<Object>(); |
296 |
| -final CountDownLatch latch = new CountDownLatch(threadCount); |
| 295 | +TestFramework.runOnce(new CacheGetWithValueLoaderIsThreadSafe((RedisCache) cache)); |
| 296 | +} |
297 | 297 |
|
298 |
| -final RedisCache redisCache = (RedisCache) cache; |
| 298 | +static class CacheGetWithValueLoaderIsThreadSafe extends MultithreadedTestCase { |
299 | 299 |
|
300 |
| -final Object key = getKey(); |
| 300 | +RedisCache redisCache; |
| 301 | +TestCacheLoader<String> cacheLoader; |
301 | 302 |
|
302 |
| -Runnable run = new Runnable() { |
303 |
| -@Override |
304 |
| -public void run() { |
305 |
| -try { |
306 |
| -Long value = redisCache.get(key, new Callable<Long>() { |
307 |
| -@Override |
308 |
| -public Long call() throws Exception { |
309 |
| - |
310 |
| -Thread.sleep(333); // make sure the thread will overlap |
311 |
| -return counter.incrementAndGet(); |
312 |
| -} |
313 |
| -}); |
314 |
| -results.add(value); |
315 |
| -} finally { |
316 |
| -latch.countDown(); |
| 303 | +public CacheGetWithValueLoaderIsThreadSafe(RedisCache redisCache) { |
| 304 | +this.redisCache = redisCache; |
| 305 | + |
| 306 | +cacheLoader = new TestCacheLoader<String>("test"){ |
| 307 | + |
| 308 | +@Override |
| 309 | +public String call() throws Exception { |
| 310 | +waitForTick(2); |
| 311 | +return super.call(); |
317 | 312 | }
|
318 |
| -} |
319 |
| -}; |
| 313 | +}; |
| 314 | +} |
320 | 315 |
|
321 |
| -for (int i = 0; i < threadCount; i++) { |
322 |
| -new Thread(run).start(); |
323 |
| -Thread.sleep(100); |
| 316 | +public void thread1(){ |
| 317 | +assertTick(0); |
| 318 | + Thread.currentThread().setName(getClass().getSimpleName() + " Cache Loader Thread"); |
| 319 | + |
| 320 | +String result = redisCache.get("key", cacheLoader); |
| 321 | + |
| 322 | +assertThat(result, is("test")); |
324 | 323 | }
|
325 |
| -latch.await(); |
326 | 324 |
|
327 |
| -assertThat(results.size(), IsEqual.equalTo(threadCount)); |
328 |
| -for (Object result : results) { |
329 |
| -assertThat((Long) result, equalTo(1L)); |
| 325 | +public void thread2(){ |
| 326 | +waitForTick(1); |
| 327 | +Thread.currentThread().setName(getClass().getSimpleName() + " Cache Reader Thread"); |
| 328 | + |
| 329 | +String result = redisCache.get("key", new TestCacheLoader<String>("illegal value")); |
| 330 | + |
| 331 | +assertThat(result, is("test")); |
| 332 | +assertTick(2); |
330 | 333 | }
|
331 | 334 | }
|
| 335 | + |
| 336 | +protected static class TestCacheLoader<T> implements Callable<T> { |
| 337 | + |
| 338 | + private final T value; |
| 339 | + |
| 340 | + public TestCacheLoader(T value) { |
| 341 | + this.value = value; |
| 342 | + } |
| 343 | + |
| 344 | + @Override |
| 345 | + public T call() throws Exception { |
| 346 | + return value; |
| 347 | + } |
| 348 | + } |
332 | 349 | }
|
0 commit comments