package org.infinispan.lock;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.infinispan.test.AbstractCacheTest;
import org.infinispan.test.AbstractInfinispanTest;
import org.infinispan.util.concurrent.TimeoutException;
import org.infinispan.util.concurrent.locks.LockManager;
import org.infinispan.util.concurrent.locks.LockPromise;
import org.infinispan.util.concurrent.locks.impl.DefaultLockManager;
import org.infinispan.util.concurrent.locks.impl.PerKeyLockContainer;
import org.infinispan.util.concurrent.locks.impl.StripedLockContainer;
import org.testng.AssertJUnit;
import org.testng.annotations.Test;
/**
* Unit tests for {@link LockManagerTest}.
*
* @author Pedro Ruivo
* @since 8.0
*/
@Test(groups = "unit", testName = "lock.LockManagerTest")
public class LockManagerTest extends AbstractInfinispanTest {
public void testSingleCounterPerKey() throws ExecutionException, InterruptedException {
DefaultLockManager lockManager = new DefaultLockManager();
PerKeyLockContainer lockContainer = new PerKeyLockContainer();
lockContainer.inject(AbstractCacheTest.TIME_SERVICE);
lockManager.inject(lockContainer, null, null);
doSingleCounterTest(lockManager);
}
public void testSingleCounterStripped() throws ExecutionException, InterruptedException {
DefaultLockManager lockManager = new DefaultLockManager();
StripedLockContainer lockContainer = new StripedLockContainer(16);
lockContainer.inject(AbstractCacheTest.TIME_SERVICE);
lockManager.inject(lockContainer, null, null);
doSingleCounterTest(lockManager);
}
public void testMultipleCounterPerKey() throws ExecutionException, InterruptedException {
DefaultLockManager lockManager = new DefaultLockManager();
PerKeyLockContainer lockContainer = new PerKeyLockContainer();
lockContainer.inject(AbstractCacheTest.TIME_SERVICE);
lockManager.inject(lockContainer, null, null);
doMultipleCounterTest(lockManager);
}
public void testMultipleCounterStripped() throws ExecutionException, InterruptedException {
DefaultLockManager lockManager = new DefaultLockManager();
StripedLockContainer lockContainer = new StripedLockContainer(16);
lockContainer.inject(AbstractCacheTest.TIME_SERVICE);
lockManager.inject(lockContainer, null, null);
doMultipleCounterTest(lockManager);
}
public void testTimeoutPerKey() throws ExecutionException, InterruptedException {
DefaultLockManager lockManager = new DefaultLockManager();
PerKeyLockContainer lockContainer = new PerKeyLockContainer();
lockContainer.inject(AbstractCacheTest.TIME_SERVICE);
lockManager.inject(lockContainer, null, null);
doTestWithFailAcquisition(lockManager);
}
public void testTimeoutStripped() throws ExecutionException, InterruptedException {
DefaultLockManager lockManager = new DefaultLockManager();
StripedLockContainer lockContainer = new StripedLockContainer(16);
lockContainer.inject(AbstractCacheTest.TIME_SERVICE);
lockManager.inject(lockContainer, null, null);
doTestWithFailAcquisition(lockManager);
}
private void doSingleCounterTest(LockManager lockManager) throws ExecutionException, InterruptedException {
final NotThreadSafeCounter counter = new NotThreadSafeCounter();
final String key = "key";
final int numThreads = 8;
final int maxCounterValue = 100;
final CyclicBarrier barrier = new CyclicBarrier(numThreads);
List<Future<Collection<Integer>>> callableResults = new ArrayList<>(numThreads);
for (int i = 0; i < numThreads; ++i) {
callableResults.add(fork(() -> {
final Thread lockOwner = Thread.currentThread();
AssertJUnit.assertEquals(0, counter.getCount());
List<Integer> seenValues = new LinkedList<>();
barrier.await();
while (true) {
lockManager.lock(key, lockOwner, 1, TimeUnit.MINUTES).lock();
AssertJUnit.assertEquals(lockOwner, lockManager.getOwner(key));
AssertJUnit.assertTrue(lockManager.isLocked(key));
AssertJUnit.assertTrue(lockManager.ownsLock(key, lockOwner));
try {
int value = counter.getCount();
if (value == maxCounterValue) {
return seenValues;
}
seenValues.add(value);
counter.setCount(value + 1);
} finally {
lockManager.unlock(key, lockOwner);
}
}
}));
}
Set<Integer> seenResults = new HashSet<>();
for (Future<Collection<Integer>> future : callableResults) {
for (Integer integer : future.get()) {
AssertJUnit.assertTrue(seenResults.add(integer));
}
}
AssertJUnit.assertEquals(maxCounterValue, seenResults.size());
for (int i = 0; i < maxCounterValue; ++i) {
AssertJUnit.assertTrue(seenResults.contains(i));
}
AssertJUnit.assertEquals(0, lockManager.getNumberOfLocksHeld());
}
private void doMultipleCounterTest(LockManager lockManager) throws ExecutionException, InterruptedException {
final int numCounters = 8;
final NotThreadSafeCounter[] counters = new NotThreadSafeCounter[numCounters];
final String[] keys = new String[numCounters];
final int numThreads = 8;
final int maxCounterValue = 100;
final CyclicBarrier barrier = new CyclicBarrier(numThreads);
for (int i = 0; i < numCounters; ++i) {
counters[i] = new NotThreadSafeCounter();
keys[i] = "key-" + i;
}
List<Future<Collection<Integer>>> callableResults = new ArrayList<>(numThreads);
for (int i = 0; i < numThreads; ++i) {
final List<String> threadKeys = new ArrayList<>(Arrays.asList(keys));
Collections.shuffle(threadKeys);
callableResults.add(fork(() -> {
final Thread lockOwner = Thread.currentThread();
List<Integer> seenValues = new LinkedList<>();
barrier.await();
while (true) {
lockManager.lockAll(threadKeys, lockOwner, 1, TimeUnit.MINUTES).lock();
for (String key : threadKeys) {
AssertJUnit.assertEquals(lockOwner, lockManager.getOwner(key));
AssertJUnit.assertTrue(lockManager.isLocked(key));
AssertJUnit.assertTrue(lockManager.ownsLock(key, lockOwner));
}
try {
int value = -1;
for (NotThreadSafeCounter counter : counters) {
if (value == -1) {
value = counter.getCount();
if (value == maxCounterValue) {
return seenValues;
}
seenValues.add(value);
} else {
AssertJUnit.assertEquals(value, counter.getCount());
}
counter.setCount(value + 1);
}
} finally {
lockManager.unlockAll(threadKeys, lockOwner);
}
}
}));
}
Set<Integer> seenResults = new HashSet<>();
for (Future<Collection<Integer>> future : callableResults) {
for (Integer integer : future.get()) {
AssertJUnit.assertTrue(seenResults.add(integer));
}
}
AssertJUnit.assertEquals(maxCounterValue, seenResults.size());
for (int i = 0; i < maxCounterValue; ++i) {
AssertJUnit.assertTrue(seenResults.contains(i));
}
AssertJUnit.assertEquals(0, lockManager.getNumberOfLocksHeld());
}
private void doTestWithFailAcquisition(LockManager lockManager) throws InterruptedException {
final String lockOwner1 = "LO1";
final String lockOwner2 = "LO2";
final String key = "key";
final String key2 = "key2";
final String key3 = "key2";
lockManager.lock(key, lockOwner1, 0, TimeUnit.MILLISECONDS).lock();
AssertJUnit.assertEquals(lockOwner1, lockManager.getOwner(key));
AssertJUnit.assertTrue(lockManager.isLocked(key));
AssertJUnit.assertTrue(lockManager.ownsLock(key, lockOwner1));
try {
lockManager.lockAll(Arrays.asList(key, key2, key3), lockOwner2, 0, TimeUnit.MILLISECONDS).lock();
AssertJUnit.assertEquals(1, lockManager.getNumberOfLocksHeld());
AssertJUnit.fail();
} catch (TimeoutException t) {
//expected
}
AssertJUnit.assertEquals(lockOwner1, lockManager.getOwner(key));
AssertJUnit.assertTrue(lockManager.isLocked(key));
AssertJUnit.assertTrue(lockManager.ownsLock(key, lockOwner1));
LockPromise lockPromise = lockManager.lockAll(Arrays.asList(key, key2, key3), lockOwner2, 1, TimeUnit.MINUTES);
AssertJUnit.assertFalse(lockPromise.isAvailable());
lockManager.unlock(key, lockOwner1);
AssertJUnit.assertTrue(lockPromise.isAvailable());
lockPromise.lock();
AssertJUnit.assertEquals(lockOwner2, lockManager.getOwner(key));
AssertJUnit.assertTrue(lockManager.isLocked(key));
AssertJUnit.assertTrue(lockManager.ownsLock(key, lockOwner2));
lockManager.unlockAll(Arrays.asList(key, key2, key3), lockOwner2);
AssertJUnit.assertEquals(0, lockManager.getNumberOfLocksHeld());
}
private static class NotThreadSafeCounter {
private int count;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
}