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