/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2009, Red Hat, Inc or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Middleware LLC.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.cache.infinispan.access;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import javax.transaction.TransactionManager;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.test.CacheManagerCallable;
import org.infinispan.test.fwk.TestCacheManagerFactory;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.hibernate.cache.infinispan.access.PutFromLoadValidator;
import org.hibernate.test.cache.infinispan.functional.cluster.DualNodeJtaTransactionManagerImpl;
import static org.infinispan.test.TestingUtil.withCacheManager;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* Tests of {@link PutFromLoadValidator}.
*
* @author Brian Stansberry
* @author Galder Zamarreño
* @version $Revision: $
*/
public class PutFromLoadValidatorUnitTestCase {
private static final Log log = LogFactory.getLog(
PutFromLoadValidatorUnitTestCase.class);
private Object KEY1 = "KEY1";
private TransactionManager tm;
@Before
public void setUp() throws Exception {
tm = DualNodeJtaTransactionManagerImpl.getInstance("test");
}
@After
public void tearDown() throws Exception {
tm = null;
try {
DualNodeJtaTransactionManagerImpl.cleanupTransactions();
}
finally {
DualNodeJtaTransactionManagerImpl.cleanupTransactionManagers();
}
}
@Test
public void testNakedPut() throws Exception {
nakedPutTest(false);
}
@Test
public void testNakedPutTransactional() throws Exception {
nakedPutTest(true);
}
private void nakedPutTest(final boolean transactional) throws Exception {
withCacheManager(new CacheManagerCallable(
TestCacheManagerFactory.createLocalCacheManager(false)) {
@Override
public void call() {
try {
PutFromLoadValidator testee = new PutFromLoadValidator(cm,
transactional ? tm : null,
PutFromLoadValidator.NAKED_PUT_INVALIDATION_PERIOD);
if (transactional) {
tm.begin();
}
boolean lockable = testee.acquirePutFromLoadLock(KEY1);
try {
assertTrue(lockable);
}
finally {
if (lockable) {
testee.releasePutFromLoadLock(KEY1);
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
}
@Test
public void testRegisteredPut() throws Exception {
registeredPutTest(false);
}
@Test
public void testRegisteredPutTransactional() throws Exception {
registeredPutTest(true);
}
private void registeredPutTest(final boolean transactional) throws Exception {
withCacheManager(new CacheManagerCallable(
TestCacheManagerFactory.createLocalCacheManager(false)) {
@Override
public void call() {
PutFromLoadValidator testee = new PutFromLoadValidator(cm,
transactional ? tm : null,
PutFromLoadValidator.NAKED_PUT_INVALIDATION_PERIOD);
try {
if (transactional) {
tm.begin();
}
testee.registerPendingPut(KEY1);
boolean lockable = testee.acquirePutFromLoadLock(KEY1);
try {
assertTrue(lockable);
}
finally {
if (lockable) {
testee.releasePutFromLoadLock(KEY1);
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
}
@Test
public void testNakedPutAfterKeyRemoval() throws Exception {
nakedPutAfterRemovalTest(false, false);
}
@Test
public void testNakedPutAfterKeyRemovalTransactional() throws Exception {
nakedPutAfterRemovalTest(true, false);
}
@Test
public void testNakedPutAfterRegionRemoval() throws Exception {
nakedPutAfterRemovalTest(false, true);
}
@Test
public void testNakedPutAfterRegionRemovalTransactional() throws Exception {
nakedPutAfterRemovalTest(true, true);
}
private void nakedPutAfterRemovalTest(final boolean transactional,
final boolean removeRegion) throws Exception {
withCacheManager(new CacheManagerCallable(
TestCacheManagerFactory.createLocalCacheManager(false)) {
@Override
public void call() {
PutFromLoadValidator testee = new PutFromLoadValidator(cm,
transactional ? tm : null,
PutFromLoadValidator.NAKED_PUT_INVALIDATION_PERIOD);
if (removeRegion) {
testee.invalidateRegion();
} else {
testee.invalidateKey(KEY1);
}
try {
if (transactional) {
tm.begin();
}
boolean lockable = testee.acquirePutFromLoadLock(KEY1);
try {
assertFalse(lockable);
}
finally {
if (lockable) {
testee.releasePutFromLoadLock(KEY1);
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
}
@Test
public void testRegisteredPutAfterKeyRemoval() throws Exception {
registeredPutAfterRemovalTest(false, false);
}
@Test
public void testRegisteredPutAfterKeyRemovalTransactional() throws Exception {
registeredPutAfterRemovalTest(true, false);
}
@Test
public void testRegisteredPutAfterRegionRemoval() throws Exception {
registeredPutAfterRemovalTest(false, true);
}
@Test
public void testRegisteredPutAfterRegionRemovalTransactional() throws Exception {
registeredPutAfterRemovalTest(true, true);
}
private void registeredPutAfterRemovalTest(final boolean transactional,
final boolean removeRegion) throws Exception {
withCacheManager(new CacheManagerCallable(
TestCacheManagerFactory.createLocalCacheManager(false)) {
@Override
public void call() {
PutFromLoadValidator testee = new PutFromLoadValidator(cm,
transactional ? tm : null,
PutFromLoadValidator.NAKED_PUT_INVALIDATION_PERIOD);
if (removeRegion) {
testee.invalidateRegion();
} else {
testee.invalidateKey(KEY1);
}
try {
if (transactional) {
tm.begin();
}
testee.registerPendingPut(KEY1);
boolean lockable = testee.acquirePutFromLoadLock(KEY1);
try {
assertTrue(lockable);
}
finally {
if (lockable) {
testee.releasePutFromLoadLock(KEY1);
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
}
@Test
public void testRegisteredPutWithInterveningKeyRemoval() throws Exception {
registeredPutWithInterveningRemovalTest(false, false);
}
@Test
public void testRegisteredPutWithInterveningKeyRemovalTransactional() throws Exception {
registeredPutWithInterveningRemovalTest(true, false);
}
@Test
public void testRegisteredPutWithInterveningRegionRemoval() throws Exception {
registeredPutWithInterveningRemovalTest(false, true);
}
@Test
public void testRegisteredPutWithInterveningRegionRemovalTransactional() throws Exception {
registeredPutWithInterveningRemovalTest(true, true);
}
private void registeredPutWithInterveningRemovalTest(
final boolean transactional, final boolean removeRegion)
throws Exception {
withCacheManager(new CacheManagerCallable(
TestCacheManagerFactory.createLocalCacheManager(false)) {
@Override
public void call() {
PutFromLoadValidator testee = new PutFromLoadValidator(cm,
transactional ? tm : null,
PutFromLoadValidator.NAKED_PUT_INVALIDATION_PERIOD);
try {
if (transactional) {
tm.begin();
}
testee.registerPendingPut(KEY1);
if (removeRegion) {
testee.invalidateRegion();
} else {
testee.invalidateKey(KEY1);
}
boolean lockable = testee.acquirePutFromLoadLock(KEY1);
try {
assertFalse(lockable);
}
finally {
if (lockable) {
testee.releasePutFromLoadLock(KEY1);
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
}
@Test
public void testDelayedNakedPutAfterKeyRemoval() throws Exception {
delayedNakedPutAfterRemovalTest(false, false);
}
@Test
public void testDelayedNakedPutAfterKeyRemovalTransactional() throws Exception {
delayedNakedPutAfterRemovalTest(true, false);
}
@Test
public void testDelayedNakedPutAfterRegionRemoval() throws Exception {
delayedNakedPutAfterRemovalTest(false, true);
}
@Test
public void testDelayedNakedPutAfterRegionRemovalTransactional() throws Exception {
delayedNakedPutAfterRemovalTest(true, true);
}
private void delayedNakedPutAfterRemovalTest(
final boolean transactional, final boolean removeRegion)
throws Exception {
withCacheManager(new CacheManagerCallable(
TestCacheManagerFactory.createLocalCacheManager(false)) {
@Override
public void call() {
PutFromLoadValidator testee = new TestValidator(cm,
transactional ? tm : null, 100);
if (removeRegion) {
testee.invalidateRegion();
} else {
testee.invalidateKey(KEY1);
}
try {
if (transactional) {
tm.begin();
}
Thread.sleep(110);
boolean lockable = testee.acquirePutFromLoadLock(KEY1);
try {
assertTrue(lockable);
}
finally {
if (lockable) {
testee.releasePutFromLoadLock(KEY1);
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
}
@Test
public void testMultipleRegistrations() throws Exception {
multipleRegistrationtest(false);
}
@Test
public void testMultipleRegistrationsTransactional() throws Exception {
multipleRegistrationtest(true);
}
private void multipleRegistrationtest(final boolean transactional) throws Exception {
withCacheManager(new CacheManagerCallable(
TestCacheManagerFactory.createLocalCacheManager(false)) {
@Override
public void call() {
final PutFromLoadValidator testee = new PutFromLoadValidator(cm,
transactional ? tm : null,
PutFromLoadValidator.NAKED_PUT_INVALIDATION_PERIOD);
final CountDownLatch registeredLatch = new CountDownLatch(3);
final CountDownLatch finishedLatch = new CountDownLatch(3);
final AtomicInteger success = new AtomicInteger();
Runnable r = new Runnable() {
public void run() {
try {
if (transactional) {
tm.begin();
}
testee.registerPendingPut(KEY1);
registeredLatch.countDown();
registeredLatch.await(5, TimeUnit.SECONDS);
if (testee.acquirePutFromLoadLock(KEY1)) {
try {
log.trace("Put from load lock acquired for key = " + KEY1);
success.incrementAndGet();
}
finally {
testee.releasePutFromLoadLock(KEY1);
}
} else {
log.trace("Unable to acquired putFromLoad lock for key = " + KEY1);
}
finishedLatch.countDown();
}
catch (Exception e) {
e.printStackTrace();
}
}
};
ExecutorService executor = Executors.newFixedThreadPool(3);
// Start with a removal so the "isPutValid" calls will fail if
// any of the concurrent activity isn't handled properly
testee.invalidateRegion();
// Do the registration + isPutValid calls
executor.execute(r);
executor.execute(r);
executor.execute(r);
try {
finishedLatch.await(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
assertEquals("All threads succeeded", 3, success.get());
}
});
}
/**
* White box test for ensuring key removals get cleaned up. <b>Note</b>: Since this test is test sensitive, if you
* add trace logging, it might fail
*
* @throws Exception
*/
@Test
public void testRemovalCleanup() throws Exception {
withCacheManager(new CacheManagerCallable(
TestCacheManagerFactory.createLocalCacheManager(false)) {
@Override
public void call() {
TestValidator testee = new TestValidator(cm, null, 200);
testee.invalidateKey("KEY1");
testee.invalidateKey("KEY2");
expectRemovalLenth(2, testee, 60000l);
assertEquals(2, testee.getRemovalQueueLength());
expectRemovalLenth(2, testee, 60000l);
assertEquals(2, testee.getRemovalQueueLength());
expectRemovalLenth(2, testee, 60000l);
}
});
}
private void expectRemovalLenth(int expectedLength, TestValidator testee, long timeout) {
long timeoutMilestone = System.currentTimeMillis() + timeout;
while ( true ) {
int queueLength = testee.getRemovalQueueLength();
if ( queueLength == expectedLength ) {
//finally it happened
return;
}
else {
if ( System.currentTimeMillis() > timeoutMilestone ) {
fail( "condition not reached after " + timeout + " milliseconds. giving up!" );
}
try {
Thread.sleep(20);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
@Test
public void testInvalidateKeyBlocksForInProgressPut() throws Exception {
invalidationBlocksForInProgressPutTest(true);
}
@Test
public void testInvalidateRegionBlocksForInProgressPut() throws Exception {
invalidationBlocksForInProgressPutTest(false);
}
private void invalidationBlocksForInProgressPutTest(final boolean keyOnly) throws Exception {
withCacheManager(new CacheManagerCallable(
TestCacheManagerFactory.createLocalCacheManager(false)) {
@Override
public void call() {
final PutFromLoadValidator testee = new PutFromLoadValidator(
cm, null, PutFromLoadValidator.NAKED_PUT_INVALIDATION_PERIOD);
final CountDownLatch removeLatch = new CountDownLatch(1);
final CountDownLatch pferLatch = new CountDownLatch(1);
final AtomicReference<Object> cache = new AtomicReference<Object>("INITIAL");
Callable<Boolean> pferCallable = new Callable<Boolean>() {
public Boolean call() throws Exception {
testee.registerPendingPut(KEY1);
if (testee.acquirePutFromLoadLock(KEY1)) {
try {
removeLatch.countDown();
pferLatch.await();
cache.set("PFER");
return Boolean.TRUE;
}
finally {
testee.releasePutFromLoadLock(KEY1);
}
}
return Boolean.FALSE;
}
};
Callable<Void> invalidateCallable = new Callable<Void>() {
public Void call() throws Exception {
removeLatch.await();
if (keyOnly) {
testee.invalidateKey(KEY1);
} else {
testee.invalidateRegion();
}
cache.set(null);
return null;
}
};
ExecutorService executorService = Executors.newCachedThreadPool();
Future<Boolean> pferFuture = executorService.submit(pferCallable);
Future<Void> invalidateFuture = executorService.submit(invalidateCallable);
try {
try {
invalidateFuture.get(1, TimeUnit.SECONDS);
fail("invalidateFuture did not block");
}
catch (TimeoutException good) {}
pferLatch.countDown();
assertTrue(pferFuture.get(5, TimeUnit.SECONDS));
invalidateFuture.get(5, TimeUnit.SECONDS);
assertNull(cache.get());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
}
private static class TestValidator extends PutFromLoadValidator {
protected TestValidator(EmbeddedCacheManager cm,
TransactionManager transactionManager,
long nakedPutInvalidationPeriod) {
super(cm, transactionManager, nakedPutInvalidationPeriod);
}
@Override
public int getRemovalQueueLength() {
return super.getRemovalQueueLength();
}
}
}