package org.marketcetera.core;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.junit.BeforeClass;
import org.junit.Test;
import org.marketcetera.module.ExpectedFailure;
import org.marketcetera.util.log.SLF4JLoggerProxy;
/* $License$ */
/**
* Tests {@link CloseableLock}.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: CloseableLockTest.java 16901 2014-05-11 16:14:11Z colin $
* @since 2.4.0
*/
public class CloseableLockTest
{
/**
* Runs once before all tests.
*
* @throws Exception if an unexpected error occurs
*/
@BeforeClass
public static void once()
throws Exception
{
LoggerConfiguration.logSetup();
}
/**
* Tests {@link CloseableLock#create(java.util.concurrent.locks.Lock)}.
*
* @throws Exception if an unexpected error occurs
*/
@Test
public void testCreate()
throws Exception
{
new ExpectedFailure<IllegalArgumentException>() {
@Override
protected void run()
throws Exception
{
CloseableLock.create(null);
}
};
assertNotNull(CloseableLock.create(new ReentrantReadWriteLock().readLock()));
}
/**
* Tests {@link CloseableLock#lock()}.
*
* @throws Exception if an unexpected error occurs
*/
@Test
public void testLock()
throws Exception
{
// try conventional first
ReadWriteLock myLock = new ReentrantReadWriteLock();
// can lock/unlock
Lock writeLock = myLock.writeLock();
writeLock.lock();
writeLock.unlock();
// verify that we can lock the wrapped lock
try(CloseableLock testLock = CloseableLock.create(myLock.writeLock())) {
testLock.lock();
}
// verify that we can lock the original lock again (wouldn't be possible if the try-with didn't unlock)
writeLock = myLock.writeLock();
writeLock.lock();
writeLock.unlock();
// repeat test with convention lock/unlock of the wrapped lock
CloseableLock wrappedLock = CloseableLock.create(myLock.writeLock());
wrappedLock.lock();
wrappedLock.unlock();
// verify that we can lock the original lock again
writeLock = myLock.writeLock();
writeLock.lock();
writeLock.unlock();
}
/**
* Tests {@link CloseableLock#lock()} with contention.
*
* @throws Exception if an unexpected error occurs
*/
@Test
public void testAlreadyLocked()
throws Exception
{
final ReadWriteLock myLock = new ReentrantReadWriteLock();
final AtomicBoolean keepLocked = new AtomicBoolean(true);
final AtomicBoolean isLocked = new AtomicBoolean(false);
Runnable locked = new Runnable() {
@Override
public void run()
{
// make this a read lock for flexibility
Lock lock = myLock.readLock();
lock.lock();
isLocked.set(true);
synchronized(isLocked) {
isLocked.notifyAll();
}
while(keepLocked.get()) {
try {
synchronized(keepLocked) {
keepLocked.wait();
}
} catch (InterruptedException e) {
SLF4JLoggerProxy.debug(CloseableLockTest.this,
e);
}
}
SLF4JLoggerProxy.debug(CloseableLockTest.this,
"Unlocking test lock");
lock.unlock();
isLocked.set(false);
synchronized(isLocked) {
isLocked.notifyAll();
}
}
};
Thread locker = new Thread(locked);
locker.start();
while(!isLocked.get()) {
synchronized(isLocked) {
isLocked.wait(250);
}
}
// lock is held in the other thread, try to lock with CloseableLock - first with read lock, which should be fine
assertTrue(isLocked.get());
try(CloseableLock testLock = CloseableLock.create(myLock.readLock())) {
testLock.lock();
}
// check that the other lock is still locked
assertTrue(isLocked.get());
// now, try a write lock (in yet another thread), which should not be possible until we unlock the thread held in the other lock
final AtomicBoolean otherLockSucceeded = new AtomicBoolean(false);
final AtomicBoolean interrupted = new AtomicBoolean(false);
Runnable otherLocked = new Runnable() {
@Override
public void run()
{
try {
try(CloseableLock testLock = CloseableLock.create(myLock.writeLock())) {
testLock.lock();
SLF4JLoggerProxy.debug(CloseableLockTest.this,
"Successfully locked other lock");
otherLockSucceeded.set(true);
synchronized(otherLockSucceeded) {
otherLockSucceeded.notifyAll();
}
}
} catch (RuntimeException e) {
if(e.getCause() instanceof InterruptedException) {
interrupted.set(true);
}
}
}
};
Thread otherLocker = new Thread(otherLocked);
otherLocker.start();
// wait a little bit
Thread.sleep(1000);
assertFalse(otherLockSucceeded.get());
// interrupt the other thread
otherLocker.interrupt();
otherLocker.join();
assertTrue(interrupted.get());
// restart the other locker
interrupted.set(false);
otherLocker = new Thread(otherLocked);
otherLocker.start();
Thread.sleep(1000);
// release the first lock
keepLocked.set(false);
synchronized(keepLocked) {
keepLocked.notifyAll();
}
// wait for the second lock to succeed
while(!otherLockSucceeded.get()) {
synchronized(otherLockSucceeded) {
otherLockSucceeded.wait(250);
}
}
assertTrue(otherLockSucceeded.get());
while(isLocked.get()) {
synchronized(isLocked) {
isLocked.wait(250);
}
}
assertFalse(isLocked.get());
// clean up
locker.interrupt();
locker.join();
otherLocker.interrupt();
otherLocker.join();
}
}