package de.axone.cache.ng; import static de.axone.cache.ng.CacheNGAssert.*; import static de.axone.cache.ng.CacheNGTestHelpers.*; import static org.assertj.core.api.Assertions.*; import java.util.Collections; import java.util.concurrent.atomic.AtomicInteger; import org.testng.annotations.Test; import de.axone.cache.ng.CacheNG.Cache; import de.axone.cache.ng.CacheNGTestHelpers.Aid; import de.axone.cache.ng.CacheNGTestHelpers.RN; import de.axone.cache.ng.CacheNGTestHelpers.TArticle; import de.axone.cache.ng.CacheNGTestHelpers.Tid; import de.axone.tools.E; @Test( groups="cacheng.timeout" ) public class CacheNGTest_TimeoutWithinMultithreaded { private static final int NUM = 10_000; private static final int THREADS = 100; private static final int TIME = 1000; //ms private volatile long start; TestAccessor accessor = new TestAccessor(); CacheNG.Cache<Aid,TArticle> cache = new CacheHashMap<>( RN.AID_ARTICLE ); CacheWrapperDelayedInvalidation<Aid,TArticle> wrapper = new CacheWrapperDelayedInvalidation<>( cache, TIME ); CacheAccessCounter<Aid,TArticle> counter = new CacheAccessCounter<>( wrapper ); CacheNG.AutomaticClient<Aid,TArticle> autoClient = new AutomaticClientImpl<>( counter ); public void fillCache(){ E.rr( "fill: " + NUM ); for( int i=0; i<NUM; i++ ){ assertThat( autoClient ) .fetch( aid( ""+i ), accessor ) .isNotNull(); } } @Test( dependsOnMethods="fillCache" ) public void invalidateAll(){ autoClient.invalidateAll( false ); } private AtomicInteger count = new AtomicInteger(), mid = new AtomicInteger(), full = new AtomicInteger() ; /* * This tests basically for no ConcurrentModificationException * by paralell execution on the same values. */ @Test( dependsOnMethods="invalidateAll" ) public void accessCacheWhileTimeoutRunning() throws InterruptedException{ Thread [] rs = new Thread[ THREADS ]; for( int t = 0; t < THREADS; t++ ){ rs[ t ] = new Thread( new Runnable(){ @Override public void run() { for( int i=0; i<NUM; i++ ){ Aid aid = aid( ""+i ); if( ! autoClient.isCached( aid ) ){ System.out.print( "." ); count.incrementAndGet(); if( System.currentTimeMillis() < start + TIME/2 ){ mid.incrementAndGet(); } if( System.currentTimeMillis() < start + TIME ){ full.incrementAndGet(); } } assertThat( autoClient ) .fetch( aid, accessor ) .isNotNull().end() ; } } } ); } E.rr( "start: " + THREADS + " threads" ); start = System.currentTimeMillis(); for( int t = 0; t < THREADS; t++ ){ rs[ t ].start(); } for( int t = 0; t < THREADS; t++ ){ rs[ t ].join(); } long end = System.currentTimeMillis(); long time = end-start; System.out.println(); // Must run longer than invalidation time to get all invalidated E.rr( "time: " + time + "ms" ); assertThat( time ).isGreaterThan( TIME ); // It's possible that parallel threads access invalid // values at the same time. so count > NUM int theCount = count.get(); E.rr( "invalid: " + theCount ); assertThat( theCount ).isGreaterThanOrEqualTo( NUM ); // Amount of values which where invalid at half the time invalidation time // Doesn't really work and don't know why... int theMid = mid.get(); E.rr( "mid: " + theMid ); //assertThat( theMid ).isBetween( theCount - theCount/2, theCount + theCount/2 ); int theFull = full.get(); E.rr( "full: " + theFull ); //assertThat( theFull ).isBetween( (int)(theMid * 1.5), (int)(theMid * 3f) ); E.rr( "Cache accesses: " + counter.count.get() ); E.rr( "Accessor accesses: " + accessor.count.get() ); } static class TestAccessor implements CacheNG.SingleValueAccessor<Aid,TArticle> { AtomicInteger count = new AtomicInteger(); @Override public TArticle fetch( Aid key ) { count.incrementAndGet(); return new TArticle( key, Collections.<Tid>emptyList() ); } } static class CacheAccessCounter<K,O> extends CacheWrapper<K,O> { AtomicInteger count = new AtomicInteger(); public CacheAccessCounter( Cache<K, O> wrapped ) { super( wrapped ); } @Override public Entry<O> fetchEntry( K key ) { count.incrementAndGet(); return super.fetchEntry( key ); } } }