/* * Copyright 2000-2013 Enonic AS * http://www.enonic.com/license */ package com.enonic.cms.framework.cache.standard; import java.util.Random; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import junit.framework.TestCase; public class StandardCacheConcurrencyTest extends TestCase { private static final Logger LOG = LoggerFactory.getLogger( StandardCacheConcurrencyTest.class.getName() ); private int keyCounter = 0; private StandardCache cache; Random wheel = new Random( 149L ); public void testConcurrencyWhenRemoveGroup() throws InterruptedException { cache = new StandardCache( 10000000 ); CacheUsageSimulator putEntrySim = new CacheUsageSimulator( cache, 10, 1000 ) { public void doCacheOperation( StandardCache cache ) { cache.put( getNewCacheEntry( "a" ) ); } }; CacheUsageSimulator removeGroupSim = new CacheUsageSimulator( cache, 1000, 10 ) { public void doCacheOperation( StandardCache cache ) { cache.removeGroup( "b" ); } }; putEntrySim.start(); removeGroupSim.start(); removeGroupSim.join(); putEntrySim.join(); assertExpectNull( putEntrySim.exception ); assertExpectNull( removeGroupSim.exception ); } public void testConcurrencyWhenGet() throws InterruptedException { cache = new StandardCache( 5 ); cache.put( createCacheEntry( "A", "1", Long.MAX_VALUE ) ); cache.put( createCacheEntry( "B", "2", Long.MAX_VALUE ) ); cache.put( createCacheEntry( "C", "3", Long.MAX_VALUE ) ); cache.put( createCacheEntry( "D", "4", Long.MAX_VALUE ) ); cache.put( createCacheEntry( "E", "5", Long.MAX_VALUE ) ); CacheUsageSimulator iterationTriggerSim = new CacheUsageSimulator( cache, 10, 1000 ) { public void doCacheOperation( StandardCache cache ) { // call remove group with any value to trigger iteration over keys cache.removeGroup( "dummy" ); } }; CacheUsageSimulator getterSim = new CacheUsageSimulator( cache, 1000, 10 ) { public void doCacheOperation( StandardCache cache ) { CacheEntry entry = cache.get( "B" ); assertNotNull( entry ); assertEquals( "B", entry.getKey().toString() ); entry = cache.get( "A" ); assertNotNull( entry ); assertEquals( "A", entry.getKey().toString() ); entry = cache.get( "E" ); assertNotNull( entry ); assertEquals( "E", entry.getKey().toString() ); entry = cache.get( "D" ); assertNotNull( entry ); assertEquals( "D", entry.getKey().toString() ); entry = cache.get( "C" ); assertNotNull( entry ); assertEquals( "C", entry.getKey().toString() ); } }; iterationTriggerSim.start(); getterSim.start(); getterSim.join(); iterationTriggerSim.join(); assertEquals( 5, cache.numberOfEntries() ); assertExpectNull( iterationTriggerSim.exception ); assertExpectNull( getterSim.exception ); } private void assertExpectNull( Exception e ) { if ( e != null ) { // print exception LOG.info( "Exception found:" , e); fail( "Concurrency test failed. Expected no exception, got: " + e.getMessage() ); } } private long getRandomSleepTime( int maxTime ) { return 1 + wheel.nextInt( maxTime ); } @SuppressWarnings({"unchecked"}) private CacheEntry createCacheEntry( String key, Object value, long timeToLive ) { return new CacheEntry( key, value, timeToLive ); } private CacheEntry getNewCacheEntry( String group ) { final String newKey = group + ":" + String.valueOf( keyCounter++ ); return createCacheEntry( newKey, "value." + newKey, Long.MAX_VALUE ); } private abstract class CacheUsageSimulator extends Thread { private int roundsToSimulate = 0; private StandardCache cache; private int sleepTime; public Exception exception; public CacheUsageSimulator( StandardCache cache, int roundsToSimulate, int sleepTime ) { this.cache = cache; this.roundsToSimulate = roundsToSimulate; this.sleepTime = sleepTime; } public void run() { int rounds = 1; while ( rounds < roundsToSimulate ) { try { doCacheOperation( cache ); } catch ( Exception e ) { exception = e; break; } rounds++; try { Thread.sleep( getRandomSleepTime( sleepTime ) ); } catch ( InterruptedException e ) { e.printStackTrace(); throw new RuntimeException( e ); } } } public abstract void doCacheOperation( StandardCache cache ); } }