I am creating an interface for executing methods concurrently, while abstracting away the synchronization details (To swap for a distributed implementation when needed). I've created a single jvm implementation that allows Strings to be used as the mutex by storing them in a map to ensure one reference is used, even if Strings of different references are passed in. The concurrency seems to work fine, however I was surprised to see that the test was showing the reference count is never decreasing. I assumed using WeakValues() would be enough to prevent memory leaks, but it seems that is not the case. Can anyone point out what could be causing this leak?
public class SynchronousMethodExecutorSynchronizedImpl implements ISynchronousMethodExecutor {
// mutex map to provide string references
final Map<String, String> mutexMap = new MapMaker()
.weakValues()
.makeComputingMap(
new Function<String, String>() {
@Override
public String apply(String id) {
return id;
}
});
@Override
public Object doSynchronousMethod(String domain, String id, ISynchronousMethod synchronousMethod) {
synchronized(mutexMap.get(domain + "." + id))
{
return synchronousMethod.execute();
}
}
}
Here is the test that is failing at the very last assertion:
public class SynchronousMethodExecutorSynchronizedImplTest extends TestCase {
int counter;
SynchronousMethodExecutorSynchronizedImpl methodExecutor;
@Override
public void before() throws Exception {
super.before();
methodExecutor = new SynchronousMethodExecutorSynchronizedImpl();
}
@Test
public void concurrentExecute() throws InterruptedException {
assertEquals(0, counter);
for(int i=0; i<1000; i++)
getConcurrentExecutorThread().start();
// wait for threads to complete
Thread.sleep(1000);
assertEquals(1, methodExecutor.mutexMap.size());
try
{
final List<long[]> infiniteList = new LinkedList<long[]>();
for(long i = Long.MIN_VALUE; i < Long.MAX_VALUE; i++)
infiniteList.add(new long[102400]);
fail("An OutOfMemoryError should be thrown");
}
catch(OutOfMemoryError e)
{
}
assertEquals(2000, counter);
assertEquals(0, methodExecutor.mutexMap.size());
}
// synchronous method
private ISynchronousMethod method = new ISynchronousMethod() {
@Override
public Object execute() {
counter++;
return null;
}
};
/**
* Executes a line of code.
*
* @return Thread
*/
private Thread getConcurrentExecutorThread() {
return new Thread() {
@Override
public void run() {
methodExecutor.doSynchronousMethod("TEST", "1", method);
try
{
Thread.sleep(500);
}
catch (InterruptedException e)
{
}
methodExecutor.doSynchronousMethod("TEST", new String("1"), method);
}
};
}
}
This last assertion is what breaks the test: assertEquals(0, methodExecutor.mutexMap.size());