Fixed LockFairnessTest - use latches instead of sleeping.

This commit is contained in:
akwizgran
2012-05-04 13:34:26 +01:00
parent 5814826573
commit 4f2704e624

View File

@@ -1,108 +1,161 @@
package net.sf.briar; package net.sf.briar;
import java.util.ArrayList; import java.util.concurrent.CountDownLatch;
import java.util.List; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.junit.After;
import org.junit.Test; import org.junit.Test;
public class LockFairnessTest extends BriarTestCase { public class LockFairnessTest extends BriarTestCase {
private final ReentrantReadWriteLock lock =
new ReentrantReadWriteLock(true); // Fair
private final List<Thread> finished = new ArrayList<Thread>();
@Test @Test
public void testReadersCanShareTheLock() throws Exception { public void testReadersCanShareTheLock() throws Exception {
// Create a long-running reader and a short-running reader // Use a fair lock
Thread longReader = new ReaderThread(lock, 100); final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
Thread shortReader = new ReaderThread(lock, 1); final CountDownLatch firstReaderHasLock = new CountDownLatch(1);
// The short-running reader should complete before the long-running one final CountDownLatch firstReaderHasFinished = new CountDownLatch(1);
longReader.start(); final CountDownLatch secondReaderHasLock = new CountDownLatch(1);
Thread.sleep(10); final CountDownLatch secondReaderHasFinished = new CountDownLatch(1);
shortReader.start(); // First reader
// Wait for the long-running reader to finish (it should finish last) Thread first = new Thread() {
longReader.join(); @Override
// The short-running reader should have finished first public void run() {
assertEquals(2, finished.size()); try {
assertEquals(shortReader, finished.get(0)); // Acquire the lock
assertEquals(longReader, finished.get(1)); lock.readLock().lock();
try {
// Allow the second reader to acquire the lock
firstReaderHasLock.countDown();
// Wait for the second reader to acquire the lock
assertTrue(secondReaderHasLock.await(10,
TimeUnit.SECONDS));
} finally {
// Release the lock
lock.readLock().unlock();
}
} catch(InterruptedException e) {
fail();
}
firstReaderHasFinished.countDown();
}
};
first.start();
// Second reader
Thread second = new Thread() {
@Override
public void run() {
try {
// Wait for the first reader to acquire the lock
assertTrue(firstReaderHasLock.await(10, TimeUnit.SECONDS));
// Acquire the lock
lock.readLock().lock();
try {
// Allow the first reader to release the lock
secondReaderHasLock.countDown();
} finally {
// Release the lock
lock.readLock().unlock();
}
} catch(InterruptedException e) {
fail();
}
secondReaderHasFinished.countDown();
}
};
second.start();
// Wait for both readers to finish
assertTrue(firstReaderHasFinished.await(10, TimeUnit.SECONDS));
assertTrue(secondReaderHasFinished.await(10, TimeUnit.SECONDS));
} }
@Test @Test
public void testWritersDoNotStarve() throws Exception { public void testWritersDoNotStarve() throws Exception {
// Create a long-running reader and a short-running reader // Use a fair lock
Thread longReader = new ReaderThread(lock, 100); final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
Thread shortReader = new ReaderThread(lock, 1); final CountDownLatch firstReaderHasLock = new CountDownLatch(1);
// Create a long-running writer final CountDownLatch firstReaderHasFinished = new CountDownLatch(1);
Thread writer = new WriterThread(lock, 100); final CountDownLatch secondReaderHasFinished = new CountDownLatch(1);
// The short-running reader should not overtake the writer and share final CountDownLatch writerHasFinished = new CountDownLatch(1);
// the lock with the long-running reader final AtomicBoolean secondReaderHasHeldLock = new AtomicBoolean(false);
longReader.start(); final AtomicBoolean writerHasHeldLock = new AtomicBoolean(false);
Thread.sleep(10); // First reader
writer.start(); Thread first = new Thread() {
Thread.sleep(10);
shortReader.start();
// Wait for the short-running reader to finish (it should finish last)
shortReader.join();
// The short-running reader should have finished last
assertEquals(3, finished.size());
assertEquals(longReader, finished.get(0));
assertEquals(writer, finished.get(1));
assertEquals(shortReader, finished.get(2));
}
@After
public void tearDown() {
finished.clear();
}
private class ReaderThread extends Thread {
private final ReentrantReadWriteLock lock;
private final int sleepTime;
private ReaderThread(ReentrantReadWriteLock lock, int sleepTime) {
this.lock = lock;
this.sleepTime = sleepTime;
}
@Override @Override
public void run() { public void run() {
try {
// Acquire the lock
lock.readLock().lock(); lock.readLock().lock();
try { try {
Thread.sleep(sleepTime); // Allow the other threads to acquire the lock
finished.add(this); firstReaderHasLock.countDown();
} catch(InterruptedException e) { // Wait for both other threads to wait for the lock
fail(); while(lock.getQueueLength() < 2) Thread.sleep(10);
// No other thread should have acquired the lock
assertFalse(secondReaderHasHeldLock.get());
assertFalse(writerHasHeldLock.get());
} finally { } finally {
// Release the lock
lock.readLock().unlock(); lock.readLock().unlock();
} }
}
}
private class WriterThread extends Thread {
private final ReentrantReadWriteLock lock;
private final int sleepTime;
private WriterThread(ReentrantReadWriteLock lock, int sleepTime) {
this.lock = lock;
this.sleepTime = sleepTime;
}
@Override
public void run() {
lock.writeLock().lock();
try {
Thread.sleep(sleepTime);
finished.add(this);
} catch(InterruptedException e) { } catch(InterruptedException e) {
fail(); fail();
}
firstReaderHasFinished.countDown();
}
};
first.start();
// Writer
Thread writer = new Thread() {
@Override
public void run() {
try {
// Wait for the first reader to acquire the lock
assertTrue(firstReaderHasLock.await(10, TimeUnit.SECONDS));
// Acquire the lock
lock.writeLock().lock();
try {
writerHasHeldLock.set(true);
// The second reader should not overtake the writer
assertFalse(secondReaderHasHeldLock.get());
} finally { } finally {
lock.writeLock().unlock(); lock.writeLock().unlock();
} }
} catch(InterruptedException e) {
fail();
} }
writerHasFinished.countDown();
}
};
writer.start();
// Second reader
Thread second = new Thread() {
@Override
public void run() {
try {
// Wait for the first reader to acquire the lock
assertTrue(firstReaderHasLock.await(10, TimeUnit.SECONDS));
// Wait for the writer to wait for the lock
while(lock.getQueueLength() < 1) Thread.sleep(10);
// Acquire the lock
lock.readLock().lock();
try {
secondReaderHasHeldLock.set(true);
// The second reader should not overtake the writer
assertTrue(writerHasHeldLock.get());
} finally {
lock.readLock().unlock();
}
} catch(InterruptedException e) {
fail();
}
secondReaderHasFinished.countDown();
}
};
second.start();
// Wait for all the threads to finish
assertTrue(firstReaderHasFinished.await(10, TimeUnit.SECONDS));
assertTrue(secondReaderHasFinished.await(10, TimeUnit.SECONDS));
assertTrue(writerHasFinished.await(10, TimeUnit.SECONDS));
} }
} }