package org.trimou.engine.cache; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicLong; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.trimou.AbstractEngineTest; /** * All {@link ComputingCache} implementations should pass this naive concurrency * test. * * @author Martin Kouba */ public class ComputingCacheTest extends AbstractEngineTest { protected static final Logger LOGGER = LoggerFactory .getLogger(ComputingCacheTest.class); @Test public void testConcurrentAccess() throws InterruptedException { long actions = 10000; int threads = 20; Result result = runComputations(threads, actions, "my", null, null); assertEquals(actions, result.getComputations().get()); assertEquals(actions, result.getCache().getAllPresent().size()); } protected Result runComputations(final int threads, final long actions, String consumerId, Long expirationTimeout, Long maxSize) throws InterruptedException { final CountDownLatch startSignal = new CountDownLatch(threads); final AtomicLong computations = new AtomicLong(); final ComputingCache<Long, String> cache = engine .getConfiguration() .getComputingCacheFactory() .create(consumerId, key -> { // logger.debug("Loading {}", key); computations.incrementAndGet(); return key + ":" + Thread.currentThread().getId(); }, expirationTimeout, maxSize, null); final ExecutorService executorService = Executors .newFixedThreadPool(threads); final List<Callable<Boolean>> tasks = new ArrayList<>(); for (int i = 0; i < threads; i++) { tasks.add(() -> { startSignal.countDown(); startSignal.await(); // Thread thread = Thread.currentThread(); // logger.debug("{}/{} started", thread.getId(), // thread.getName()); for (long j = 0; j < actions; j++) { cache.get(j); } // logger.debug("{}/{} finished", thread.getId(), // thread.getName()); return true; }); } List<Future<Boolean>> results = executorService.invokeAll(tasks); while (!isFinished(results)) { Thread.sleep(10L); } return new Result(cache, computations); } private boolean isFinished(List<Future<Boolean>> results) { for (Future<Boolean> future : results) { if (!future.isDone()) { return false; } } return true; } protected static class Result { private final ComputingCache<Long, String> cache; private final AtomicLong computations; public Result(ComputingCache<Long, String> cache, AtomicLong computations) { super(); this.cache = cache; this.computations = computations; } public ComputingCache<Long, String> getCache() { return cache; } public AtomicLong getComputations() { return computations; } } @Test public void testInvalidate() { final ComputingCache<Long, String> cache = engine.getConfiguration() .getComputingCacheFactory() .create("test", key -> "" + key, null, null, null); for (long i = 0; i < 100; i++) { cache.get(i); } cache.invalidate(key -> key % 2 == 0); assertEquals(50, cache.size()); } @Test public void testEviction() throws InterruptedException { long actions = 1000L; int threads = 10; Result result = runComputations(threads, actions, "test", null, 500L); assertTrue(result.getComputations().get() >= actions); assertTrue(result.getCache().size() < actions); } }