Polishing.

Inline valueFromLoader method. Refine tests to not rely on the number of runtime CPU cores.

See #2079
Original pull request: #2082.
This commit is contained in:
Mark Paluch
2021-06-15 09:29:04 +02:00
parent 30aeda562b
commit ba767fa86d
2 changed files with 88 additions and 34 deletions

View File

@@ -132,13 +132,19 @@ public class RedisCache extends AbstractValueAdaptingCache {
@SuppressWarnings("unchecked")
private synchronized <T> T getSynchronized(Object key, Callable<T> valueLoader) {
ValueWrapper result = get(key);
if (result != null) {
return (T) result.get();
}
T value = valueFromLoader(key, valueLoader);
T value;
try {
value = valueLoader.call();
} catch (Exception e) {
throw new ValueRetrievalException(key, valueLoader, e);
}
put(key, value);
return value;
}
@@ -391,12 +397,4 @@ public class RedisCache extends AbstractValueAdaptingCache {
return cacheConfig.getKeyPrefixFor(name) + key;
}
private static <T> T valueFromLoader(Object key, Callable<T> valueLoader) {
try {
return valueLoader.call();
} catch (Exception e) {
throw new ValueRetrievalException(key, valueLoader, e);
}
}
}

View File

@@ -18,6 +18,7 @@ package org.springframework.data.redis.cache;
import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.api.Assumptions.*;
import io.netty.util.concurrent.DefaultThreadFactory;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@@ -25,15 +26,18 @@ import lombok.RequiredArgsConstructor;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.stream.Stream;
import java.util.stream.IntStream;
import org.junit.jupiter.api.BeforeEach;
@@ -48,6 +52,7 @@ import org.springframework.data.redis.serializer.RedisSerializationContext.Seria
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.test.extension.parametrized.MethodSource;
import org.springframework.data.redis.test.extension.parametrized.ParameterizedRedisTest;
import org.springframework.lang.Nullable;
/**
* Tests for {@link RedisCache} with {@link DefaultRedisCacheWriter} using different {@link RedisSerializer} and
@@ -55,6 +60,7 @@ import org.springframework.data.redis.test.extension.parametrized.ParameterizedR
*
* @author Christoph Strobl
* @author Mark Paluch
* @author Piotr Mionskowski
*/
@MethodSource("testParams")
public class RedisCacheTests {
@@ -415,35 +421,85 @@ public class RedisCacheTests {
}
@ParameterizedRedisTest // GH-2079
void multipleThreadsLoadValueOnce() {
void multipleThreadsLoadValueOnce() throws InterruptedException {
int threadCount = 5;
int threadCount = 2;
ConcurrentMap<Integer, Integer> valuesByThreadId = new ConcurrentHashMap<>(threadCount);
CountDownLatch prepare = new CountDownLatch(threadCount);
CountDownLatch prepareForReturn = new CountDownLatch(1);
CountDownLatch finished = new CountDownLatch(threadCount);
AtomicInteger retrievals = new AtomicInteger();
AtomicReference<byte[]> storage = new AtomicReference<>();
CountDownLatch waiter = new CountDownLatch(threadCount);
cache = new RedisCache("foo", new RedisCacheWriter() {
@Override
public void put(String name, byte[] key, byte[] value, @Nullable Duration ttl) {
storage.set(value);
}
AtomicInteger threadIds = new AtomicInteger(0);
@Override
public byte[] get(String name, byte[] key) {
AtomicInteger currentValueForKey = new AtomicInteger(0);
prepare.countDown();
try {
prepareForReturn.await(1, TimeUnit.MINUTES);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
Stream.generate(threadIds::getAndIncrement)
.limit(threadCount)
.parallel()
.forEach((threadId) -> {
waiter.countDown();
try {
waiter.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
Integer valueForThread = cache.get("key", currentValueForKey::incrementAndGet);
valuesByThreadId.put(threadId, valueForThread);
});
return storage.get();
}
valuesByThreadId.forEach((thread, valueForThread) -> {
assertThat(valueForThread).isEqualTo(currentValueForKey.get());
});
@Override
public byte[] putIfAbsent(String name, byte[] key, byte[] value, @Nullable Duration ttl) {
return new byte[0];
}
@Override
public void remove(String name, byte[] key) {
}
@Override
public void clean(String name, byte[] pattern) {
}
@Override
public void clearStatistics(String name) {
}
@Override
public RedisCacheWriter withStatisticsCollector(CacheStatisticsCollector cacheStatisticsCollector) {
return null;
}
@Override
public CacheStatistics getCacheStatistics(String cacheName) {
return null;
}
}, RedisCacheConfiguration.defaultCacheConfig());
ThreadPoolExecutor tpe = new ThreadPoolExecutor(threadCount, threadCount, 1, TimeUnit.MINUTES,
new LinkedBlockingDeque<>(), new DefaultThreadFactory("RedisCacheTests"));
IntStream.range(0, threadCount).forEach(it -> tpe.submit(() -> {
cache.get("foo", retrievals::incrementAndGet);
finished.countDown();
}));
// wait until all Threads have arrived in RedisCacheWriter.get(…)
prepare.await();
// let all threads continue
prepareForReturn.countDown();
// wait until ThreadPoolExecutor has completed.
finished.await();
tpe.shutdown();
assertThat(retrievals).hasValue(1);
}
void doWithConnection(Consumer<RedisConnection> callback) {