Hi Ben, this codes reproduces the issue. It works almost always to me.
package com.openwave.src;
import java.util.concurrent.Callable;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import junit.framework.Assert;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
import com.googlecode.concurrentlinkedhashmap.Weigher;
/**
* Reproduce almost always the race condition for issue: (20)
* Link:
http://code.google.com/p/concurrentlinkedhashmap/issues/detail?id=20
*
* Tested with Dual Core 2.79 Ghz and 3GB RAM.
* For more cores the number of threads(MAX_THREADS) might need to be incremented
* and perhaps the number of cycles (CYCLES) as well.
*
* The worker threads perform #get against the cache and the main threads does a #put continuously.
*
* @author pechague (Patricio Echague)
*
*/
public class EvictRaceConditionTest {
/**
* a map of the cache entries
*/
private ConcurrentLinkedHashMap<String, Entry> cache;
private ThreadPoolExecutor pool;
private Entry onlyEntry;
private static final String UNIQUE_KEY = "theOnlyKey";
private static final int CYCLES = 4000000;
private static final int MAX_THREADS = 20;
/**
* @throws java.lang.Exception
*/
@Before
public void setUp() throws Exception {
pool = new ThreadPoolExecutor(MAX_THREADS, MAX_THREADS, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
// Define my own weigher.
Weigher<Entry> weigher = new Weigher<Entry>() {
public int weightOf(Entry userEntry) {
return userEntry.size;
}
};
cache = new ConcurrentLinkedHashMap.Builder<String, Entry>()
.weigher(weigher)
.maximumWeightedCapacity((int) 1)
.initialCapacity(1)
.build();
onlyEntry = new Entry();
onlyEntry.setSize(1);
}
/**
* @throws java.lang.Exception
*/
@After
public void tearDown() throws Exception {
pool.shutdownNow();
while (pool.getPoolSize() > 0) { /* spin until terminated */ }
}
@Test
public void testRaceCondition() throws Exception {
for (int t = 0; t < MAX_THREADS; t++) {
pool.submit(new CacheGetWorker());
}
Thread.sleep(1);
System.out.println("Start writer");
try {
for (int i = 0; i < CYCLES; i++) {
if (onlyEntry.getSize() == 1) {
onlyEntry.setSize(2);
} else {
onlyEntry.setSize(1);
}
cache.put(UNIQUE_KEY, onlyEntry);
}
// The code should not reach this point.
Assert.fail();
} catch (NullPointerException npe) {
System.out.println("Exception caught!");
npe.printStackTrace();
}
System.out.println("Done Writer");
}
/**
* This worker put the same object again and again but varies the size from 1 to 2 to generate an eviction.
* @author pechague
*
*/
private class CacheGetWorker implements Callable<Boolean> {
@Override
public Boolean call() throws Exception {
System.out.println("Start Worker!");
Boolean thereWasAnError = Boolean.FALSE;
for (int i = 0; i < CYCLES; i++) {
try {
cache.get(UNIQUE_KEY);
} catch (NullPointerException npe) {
System.out.println("Error found in thread:!" + Thread.currentThread().getId());
thereWasAnError = Boolean.TRUE;
break;
} catch (Exception e) {
thereWasAnError = Boolean.TRUE;
System.out.println("Exception not expected in thread:!" + Thread.currentThread().getId());
e.printStackTrace();
break;
}
}
System.out.println("Done worker");
return thereWasAnError;
}
}
/**
*
* @author pechague (Patricio Echague)
*
*/
private class Entry {
private volatile int size = 1;
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
--
Patricio.-