diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/cache/SearchCache.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/cache/SearchCache.java index 26aff5b31bb0f69ed69d721d538d1146edf634da..5eddfc57f6caaa23bed67eb4eaf407cc3136c16c 100644 --- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/cache/SearchCache.java +++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/cache/SearchCache.java @@ -130,7 +130,7 @@ public class SearchCache<CRITERIA, FETCH_OPTIONS, OBJECT> implements ISearchCach return getCacheManager().getCache(CACHE_NAME); } - private long getCacheSize() + protected long getCacheSize() { String propertyValue = getSystemProperty(CACHE_SIZE_PROPERTY_NAME); @@ -164,7 +164,7 @@ public class SearchCache<CRITERIA, FETCH_OPTIONS, OBJECT> implements ISearchCach } } - private long getCacheDefaultSize() + protected long getCacheDefaultSize() { long memorySize = getMemorySize(); long cacheSize = memorySize / 4; diff --git a/openbis/sourceTest/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/method/AbstractSearchMethodExecutorStressTest.java b/openbis/sourceTest/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/method/AbstractSearchMethodExecutorStressTest.java index 66f736722903c97f6ff23ee8bb013b049a3c5246..b5e935c78402e1b6fa5e06214b565b800fb9b0ff 100644 --- a/openbis/sourceTest/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/method/AbstractSearchMethodExecutorStressTest.java +++ b/openbis/sourceTest/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/method/AbstractSearchMethodExecutorStressTest.java @@ -27,6 +27,7 @@ import java.util.UUID; import net.sf.ehcache.CacheManager; +import org.apache.commons.io.FileUtils; import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -55,6 +56,7 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.Session; /** * @author pkupczyk */ +@SuppressWarnings({ "unchecked", "rawtypes" }) public class AbstractSearchMethodExecutorStressTest { @@ -64,105 +66,145 @@ public class AbstractSearchMethodExecutorStressTest LogInitializer.init(); } - @SuppressWarnings({ "unchecked", "rawtypes" }) @Test - public void testMultipleThreads() throws InterruptedException + public void testConcurrencyWithoutEvicting() throws InterruptedException { - int SESSION_COUNT = 5; - int THREAD_COUNT = 5; - int KEY_VERSION_COUNT = 20; - int RUN_COUNT = 5; - - for (int r = 0; r < RUN_COUNT; r++) + for (int run = 0; run < 5; run++) { - final TestSearchMethodExecutor executor = new TestSearchMethodExecutor(); + TestSearchMethodExecutor executor = testConcurrency(0); - final List<String> sessionTokens = new ArrayList<String>(); - for (int s = 0; s < SESSION_COUNT; s++) + for (Map.Entry<SearchCacheKey, Integer> entry : executor.getSearchCounts().entrySet()) { - Session session = new Session("user" + s, "token" + s, new Principal(), "", 1); - sessionTokens.add(session.getSessionToken()); - executor.addSession(session.getSessionToken(), session); + if (entry.getValue() != 1) + { + executor.addError("Run " + run + " : " + entry.getValue() + " searches were executed instead of 1 for the key " + entry.getKey()); + } } - final List<SearchCacheKey> keys = new ArrayList<SearchCacheKey>(); - for (String sessionToken : sessionTokens) + if (executor.getErrors().size() > 0) { - for (int k = 0; k < KEY_VERSION_COUNT; k++) - { - SpaceSearchCriteria spaceSearchCriteria = new SpaceSearchCriteria(); - spaceSearchCriteria.withCode().thatEquals(String.valueOf(k)); - keys.add(new SearchCacheKey(sessionToken, spaceSearchCriteria, new SpaceFetchOptions().cacheMode(CacheMode.CACHE))); - - ProjectSearchCriteria projectSearchCriteria = new ProjectSearchCriteria(); - projectSearchCriteria.withCode().thatEquals(String.valueOf(k)); - keys.add(new SearchCacheKey(sessionToken, projectSearchCriteria, new ProjectFetchOptions().cacheMode(CacheMode.CACHE))); + Assert.fail(String.join("\n", executor.getErrors())); + } + } + } - ExperimentSearchCriteria experimentSearchCriteria = new ExperimentSearchCriteria(); - experimentSearchCriteria.withCode().thatEquals(String.valueOf(k)); - keys.add(new SearchCacheKey(sessionToken, experimentSearchCriteria, new ExperimentFetchOptions().cacheMode(CacheMode.CACHE))); + @Test + public void testConcurrencyWithEvicting() throws InterruptedException + { + for (int run = 0; run < 5; run++) + { + TestSearchMethodExecutor executor = testConcurrency(10 * FileUtils.ONE_KB); - SampleSearchCriteria sampleSearchCriteria = new SampleSearchCriteria(); - sampleSearchCriteria.withCode().thatEquals(String.valueOf(k)); - keys.add(new SearchCacheKey(sessionToken, sampleSearchCriteria, new SampleFetchOptions().cacheMode(CacheMode.CACHE))); - } + if (executor.getErrors().size() > 0) + { + Assert.fail(String.join("\n", executor.getErrors())); } + } + } + + private TestSearchMethodExecutor testConcurrency(long cacheSize) throws InterruptedException + { + int SESSION_COUNT = 5; + int THREAD_COUNT = 5; + int KEY_VERSION_COUNT = 20; - List<Thread> threads = new ArrayList<Thread>(); + final TestSearchMethodExecutor executor = new TestSearchMethodExecutor(cacheSize); - for (int t = 0; t < THREAD_COUNT; t++) + final List<String> sessionTokens = new ArrayList<String>(); + for (int s = 0; s < SESSION_COUNT; s++) + { + Session session = new Session("user" + s, "token" + s, new Principal(), "", 1); + sessionTokens.add(session.getSessionToken()); + executor.addSession(session.getSessionToken(), session); + } + + final List<SearchCacheKey> keys = new ArrayList<SearchCacheKey>(); + for (String sessionToken : sessionTokens) + { + for (int k = 0; k < KEY_VERSION_COUNT; k++) { - threads.add(new Thread(new Runnable() + SpaceSearchCriteria spaceSearchCriteria = new SpaceSearchCriteria(); + spaceSearchCriteria.withCode().thatEquals(String.valueOf(k)); + SearchCacheKey spaceKey = + new SearchCacheKey(sessionToken, spaceSearchCriteria, new SpaceFetchOptions().cacheMode(CacheMode.CACHE)); + keys.add(spaceKey); + executor.setSearchResult(spaceKey, new RandomSizeArray(cacheSize)); + + ProjectSearchCriteria projectSearchCriteria = new ProjectSearchCriteria(); + projectSearchCriteria.withCode().thatEquals(String.valueOf(k)); + SearchCacheKey projectKey = + new SearchCacheKey(sessionToken, projectSearchCriteria, new ProjectFetchOptions().cacheMode(CacheMode.CACHE)); + keys.add(projectKey); + executor.setSearchResult(projectKey, new RandomSizeArray(cacheSize)); + + ExperimentSearchCriteria experimentSearchCriteria = new ExperimentSearchCriteria(); + experimentSearchCriteria.withCode().thatEquals(String.valueOf(k)); + SearchCacheKey experimentKey = + new SearchCacheKey(sessionToken, experimentSearchCriteria, new ExperimentFetchOptions().cacheMode(CacheMode.CACHE)); + keys.add(experimentKey); + executor.setSearchResult(experimentKey, new RandomSizeArray(cacheSize)); + + SampleSearchCriteria sampleSearchCriteria = new SampleSearchCriteria(); + sampleSearchCriteria.withCode().thatEquals(String.valueOf(k)); + SearchCacheKey sampleKey = + new SearchCacheKey(sessionToken, sampleSearchCriteria, new SampleFetchOptions().cacheMode(CacheMode.CACHE)); + keys.add(sampleKey); + executor.setSearchResult(sampleKey, new RandomSizeArray(cacheSize)); + } + } + + List<Thread> threads = new ArrayList<Thread>(); + + for (int t = 0; t < THREAD_COUNT; t++) + { + threads.add(new Thread(new Runnable() + { + @Override + public void run() { - @Override - public void run() + for (int i = 0; i < keys.size() * 2; i++) { - for (int i = 0; i < keys.size() * 2; i++) + SearchCacheKey key = keys.get((int) (Math.random() * keys.size())); + SearchResult searchResult = executor.search(key.getSessionToken(), key.getCriteria(), key.getFetchOptions()); + + Object actualResult = searchResult.getObjects().get(0); + Object expectedResult = executor.getSearchResult(key); + + if (false == actualResult.equals(expectedResult)) { - SearchCacheKey key = keys.get((int) (Math.random() * keys.size())); - SearchResult searchResult = executor.search(key.getSessionToken(), key.getCriteria(), key.getFetchOptions()); - Assert.assertEquals(searchResult.getObjects().get(0), key); + executor.addError("Actual search result: " + actualResult + " but expected: " + expectedResult + " for key: " + + key); } - } - })); - } - - for (Thread thread : threads) - { - thread.start(); - } - for (Thread thread : threads) - { - thread.join(); - } + } + })); + } - StringBuilder error = new StringBuilder(); - for (Map.Entry<SearchCacheKey, Integer> entry : executor.getSearchCounts().entrySet()) - { - if (entry.getValue() != 1) - { - error.append("Run " + r + " : " + entry.getValue() + " searches were executed instead of 1 for the key " + entry.getKey() - + "\n"); - } - } + for (Thread thread : threads) + { + thread.start(); + } - if (error.length() > 0) - { - Assert.fail(error.toString()); - } + for (Thread thread : threads) + { + thread.join(); } + + return executor; } - @SuppressWarnings({ "rawtypes", "unchecked" }) - private class TestSearchMethodExecutor extends AbstractSearchMethodExecutor + private static class TestSearchMethodExecutor extends AbstractSearchMethodExecutor { private Map<String, Session> sessions = new HashMap<>(); private Map<SearchCacheKey, Integer> searchCounts = new HashMap<>(); - public TestSearchMethodExecutor() + private Map<SearchCacheKey, Object> searchResults = new HashMap<>(); + + private List<String> errors = Collections.synchronizedList(new ArrayList<String>()); + + public TestSearchMethodExecutor(final long cacheSize) { String managerConfig = "<ehcache name='" + UUID.randomUUID() + "'></ehcache>"; final CacheManager manager = new CacheManager(new ByteArrayInputStream(managerConfig.getBytes())); @@ -174,7 +216,20 @@ public class AbstractSearchMethodExecutorStressTest { return manager; } + + @Override + protected long getCacheSize() + { + if (cacheSize > 0) + { + return cacheSize; + } else + { + return super.getCacheSize(); + } + } }; + theCache.initCache(); this.cache = theCache; } @@ -200,7 +255,7 @@ public class AbstractSearchMethodExecutorStressTest throw new RuntimeException(e); } - return Collections.singleton(key); + return Collections.singleton(searchResults.get(key)); } public Map<SearchCacheKey, Integer> getSearchCounts() @@ -208,6 +263,16 @@ public class AbstractSearchMethodExecutorStressTest return searchCounts; } + public void setSearchResult(SearchCacheKey key, Object result) + { + searchResults.put(key, result); + } + + public Object getSearchResult(SearchCacheKey key) + { + return searchResults.get(key); + } + void addSession(String sessionToken, Session session) { sessions.put(sessionToken, session); @@ -219,6 +284,16 @@ public class AbstractSearchMethodExecutorStressTest return sessions.get(sessionToken); } + public void addError(String error) + { + errors.add(error); + } + + public List<String> getErrors() + { + return errors; + } + @Override protected ISearchObjectExecutor getSearchExecutor() { @@ -245,4 +320,35 @@ public class AbstractSearchMethodExecutorStressTest } + private static class RandomSizeArray + { + + private byte[] array; + + public RandomSizeArray(long maxSize) + { + array = new byte[(int) (Math.random() * maxSize)]; + } + + @Override + public int hashCode() + { + return array.length; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + RandomSizeArray other = (RandomSizeArray) obj; + return array.length == other.array.length; + } + + } + }