/*
* The terms of the JPOX License are distributed with the software documentation.
*/
package org.jpox.util;
import java.util.Random;
import org.jpox.util.ReadWriteLock;
import junit.framework.Assert;
import junit.framework.TestCase;
/**
* Tests the functionality of a {@link ReadWriteLock}.
*
* @author <a href="mailto:mmartin5@austin.rr.com">Mike Martin</a>
*/
public class ReadWriteLockTest extends TestCase
{
private static final int MAX_TEST_TIME = 30;
private static final int NUM_TEST_THREADS = 10;
private static final int NUM_OUTER_ITERATIONS = 500;
private static final int NUM_INNER_ITERATIONS = 2500;
private static final int YIELD_FREQUENCY = 250;
private Throwable threadFailure;
/**
* Used by the JUnit framework to construct tests. Normally, programmers
* would never explicitly use this constructor.
*
* @param name Name of the <tt>TestCase</tt>.
*/
public ReadWriteLockTest(String name)
{
super(name);
}
public void testBasicFunction() throws Throwable
{
ThreadGroup group = new ThreadGroup("ReadWriteLockTest");
Thread[] threads = new Thread[NUM_TEST_THREADS];
ReadWriteLock rwl = new ReadWriteLock();
Object[] objs = new Object[1];
/*
* Create and start all the threads running the test.
*/
for (int i = 0; i < NUM_TEST_THREADS; ++i)
threads[i] = new Thread(group, new TestThread(new Random(i), rwl, objs), "Test thread #" + i);
threadFailure = null;
for (int i = 0; i < NUM_TEST_THREADS; ++i)
threads[i].start();
/*
* Wait up to MAX_TEST_TIME seconds for all threads to finish. Any
* longer is assumed to be a failure.
*/
long startTime = System.currentTimeMillis();
int runningThreads;
do
{
runningThreads = group.enumerate(threads);
for (int i = 0; i < runningThreads; ++i)
{
threads[i].join(250);
if (System.currentTimeMillis() - startTime > MAX_TEST_TIME * 1000)
{
for (int j = 0; j < runningThreads; ++j)
threads[j].interrupt();
fail("Test has taken more than " + MAX_TEST_TIME + " seconds; it is assumed to be deadlocked");
}
}
} while (runningThreads > 0);
/*
* If any thread threw an exception, rethrow the last one that
* occurred.
*/
if (threadFailure != null)
throw threadFailure;
}
private class TestThread implements Runnable
{
private final Random rnd;
private final ReadWriteLock rwl;
private final Object[] objs;
public TestThread(Random rnd, ReadWriteLock rwl, Object[] objs)
{
this.rnd = rnd;
this.rwl = rwl;
this.objs = objs;
}
public void run()
{
try
{
/*
* Test various legal and illegal nested locking patterns
* repeatedly in a random order.
*/
final String[] lockPatterns =
{
/* Legal patterns */
"R", "W",
"RR", "WR", "WW",
"RRR", "WRR", "WWR", "WWW",
/* Illegal patterns */
"RW", "RRW", "RRRW", "RRRRW"
};
for (int i = 0; i < NUM_OUTER_ITERATIONS && threadFailure == null; ++i)
{
String pattern = lockPatterns[rnd.nextInt(lockPatterns.length)];
boolean isLegalPattern = pattern.indexOf("RW") == -1;
try
{
tryPattern(pattern);
if (!isLegalPattern)
fail("Illegal nested lock sequence " + pattern + " should have thrown an IllegalStateException");
}
catch (IllegalStateException e)
{
if (isLegalPattern)
throw e;
}
}
}
catch (Throwable t)
{
threadFailure = t;
}
}
private void tryPattern(String lockPattern) throws InterruptedException
{
boolean isWriteLock = lockPattern.charAt(0) == 'W';
if (isWriteLock)
rwl.writeLock();
else
rwl.readLock();
try
{
Object obj;
/*
* If writing, assign a new value to the array, else read the
* existing value.
*/
if (isWriteLock)
{
obj = this;
objs[0] = obj;
}
else
obj = objs[0];
/*
* Assert that the current value doesn't change over many
* successive accesses. Occasionally yield to other threads.
*/
for (int i = 0; i < NUM_INNER_ITERATIONS; ++i)
{
Assert.assertSame(obj, objs[0]);
if (i % YIELD_FREQUENCY == 0)
Thread.yield();
}
/*
* If the pattern calls for nested locks, recurse.
*/
if (lockPattern.length() > 1)
tryPattern(lockPattern.substring(1));
}
finally
{
rwl.unlock();
}
}
}
}