package org.corfudb.infrastructure.log;
import lombok.extern.slf4j.Slf4j;
import org.corfudb.AbstractCorfuTest;
import org.junit.Test;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* Created by kspirov on 3/8/17.
*/
@Slf4j
public class MultiReadWriteLockTest extends AbstractCorfuTest {
@Test
public void testWriteAndReadLocksAreReentrant() throws Exception {
CallableConsumer c = (r) -> {
MultiReadWriteLock locks = new MultiReadWriteLock();
try (MultiReadWriteLock.AutoCloseableLock ignored1 = locks.acquireWriteLock(1l)) {
try (MultiReadWriteLock.AutoCloseableLock ignored2 = locks.acquireWriteLock(1l)) {
try (MultiReadWriteLock.AutoCloseableLock ignored3 = locks.acquireReadLock(1l)) {
try (MultiReadWriteLock.AutoCloseableLock ignored4 = locks.acquireReadLock(1l)) {
}
}
}
}
};
scheduleConcurrently(PARAMETERS.CONCURRENCY_ONE, c);
executeScheduled(PARAMETERS.CONCURRENCY_ONE, PARAMETERS.TIMEOUT_NORMAL);
// victory - we were not canceled
}
@Test
public void testIndependentWriteLocksDoNotSynchronize() throws Exception {
MultiReadWriteLock locks = new MultiReadWriteLock();
CyclicBarrier entry = new CyclicBarrier(PARAMETERS.CONCURRENCY_SOME);
// Here test that the lock does not synchronize when the value is different.
// Otherwise one of the locks would halt on await, and the others - when acquiring the write log.
CallableConsumer c = (r) -> {
try(MultiReadWriteLock.AutoCloseableLock ignored = locks.acquireWriteLock((long)r)){
entry.await();
}
};
scheduleConcurrently(PARAMETERS.CONCURRENCY_SOME, c);
executeScheduled(PARAMETERS.CONCURRENCY_SOME, PARAMETERS.TIMEOUT_NORMAL);
// victory - we were not canceled
}
@Test
public void testWriteLockSynchronizes() throws Exception {
MultiReadWriteLock locks = new MultiReadWriteLock();
CyclicBarrier entry = new CyclicBarrier(PARAMETERS.CONCURRENCY_SOME);
// All threads should block, nobody should exit
AtomicBoolean noProblems = new AtomicBoolean(true);
CallableConsumer c = (r) -> {
try(MultiReadWriteLock.AutoCloseableLock ignored = locks.acquireWriteLock(1l)) {
entry.await();
noProblems.set(false);
}
};
scheduleConcurrently(PARAMETERS.CONCURRENCY_SOME, c);
try {
executeScheduled(PARAMETERS.CONCURRENCY_SOME, PARAMETERS.TIMEOUT_VERY_SHORT);
fail();
} catch (CancellationException e) {
assertTrue(noProblems.get());
}
}
@Test
public void testReadLock() throws Exception {
MultiReadWriteLock locks = new MultiReadWriteLock();
CyclicBarrier entry = new CyclicBarrier(PARAMETERS.CONCURRENCY_SOME);
CallableConsumer c = (r) -> {
try(MultiReadWriteLock.AutoCloseableLock ignored = locks.acquireReadLock(1l)){
entry.await();
}
};
scheduleConcurrently(PARAMETERS.CONCURRENCY_SOME, c);
executeScheduled(PARAMETERS.CONCURRENCY_SOME, PARAMETERS.TIMEOUT_NORMAL);
}
@Test
public void testWriteLockNotPermittedInReadLock() {
MultiReadWriteLock locks = new MultiReadWriteLock();
try (MultiReadWriteLock.AutoCloseableLock ignored1 = locks.acquireReadLock(1l)) {
try (MultiReadWriteLock.AutoCloseableLock ignored2 = locks.acquireWriteLock(1l)) {
}
fail();
} catch (IllegalStateException e) {
// expected
}
}
@Test
public void testIncorrectLockOrder() {
MultiReadWriteLock locks = new MultiReadWriteLock();
try (MultiReadWriteLock.AutoCloseableLock ignored1 = locks.acquireReadLock(2l)) {
try (MultiReadWriteLock.AutoCloseableLock ignored2 = locks.acquireWriteLock(1l)) {
}
fail();
} catch (IllegalStateException e) {
// expected
}
}
@Test
public void testCorrectLockOrder() {
MultiReadWriteLock locks = new MultiReadWriteLock();
try (MultiReadWriteLock.AutoCloseableLock ignored1 = locks.acquireReadLock(1l)) {
try (MultiReadWriteLock.AutoCloseableLock ignored2 = locks.acquireWriteLock(2l)) {
}
}
// no RuntimeException as expected
}
@Test
public void testLockCorrectlyDeregistered() {
MultiReadWriteLock locks = new MultiReadWriteLock();
try (MultiReadWriteLock.AutoCloseableLock ignored1 = locks.acquireReadLock(2l)) {
}
try (MultiReadWriteLock.AutoCloseableLock ignored2 = locks.acquireWriteLock(1l)) {
}
// let's nest
try (MultiReadWriteLock.AutoCloseableLock ignored0 = locks.acquireReadLock(0l)) {
try (MultiReadWriteLock.AutoCloseableLock ignored1 = locks.acquireReadLock(2l)) {
}
try (MultiReadWriteLock.AutoCloseableLock ignored2 = locks.acquireWriteLock(1l)) {
}
}
// no RuntimeException as expected
}
@Test
public void testLockCloseIsIdempotent() {
MultiReadWriteLock locks = new MultiReadWriteLock();
MultiReadWriteLock.AutoCloseableLock ignored1 = locks.acquireWriteLock(2l);
ignored1.close();
ignored1.close();
MultiReadWriteLock.AutoCloseableLock ignored2 = locks.acquireReadLock(1l);
ignored2.close();
ignored2.close();
}
@Test
public void testWrongUnlocksOrderCatched() {
MultiReadWriteLock locks = new MultiReadWriteLock();
MultiReadWriteLock.AutoCloseableLock ignored1 = locks.acquireWriteLock(1l);
MultiReadWriteLock.AutoCloseableLock ignored2 = locks.acquireReadLock(2l);
try {
ignored1.close();
fail();
} catch (IllegalStateException e) {
// expected
}
}
@Test
public void testWrongUnlocksTypeCatched() {
MultiReadWriteLock locks = new MultiReadWriteLock();
MultiReadWriteLock.AutoCloseableLock ignored1 = locks.acquireWriteLock(1l);
MultiReadWriteLock.AutoCloseableLock ignored2 = locks.acquireReadLock(1l);
try {
ignored1.close();
fail();
} catch (IllegalStateException e) {
// expected
}
}
}