/*
* JBoss, Home of Professional Open Source
* Copyright 2009 Red Hat Inc. and/or its affiliates and other
* contributors as indicated by the @author tags. All rights reserved.
* See the copyright.txt in the distribution for a full listing of
* individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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 software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.infinispan.distribution.rehash;
import org.infinispan.Cache;
import org.infinispan.distribution.BaseDistFunctionalTest;
import org.infinispan.distribution.MagicKey;
import org.infinispan.test.TestingUtil;
import org.testng.annotations.Test;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.testng.Assert.assertEquals;
/**
* A base test for all rehashing tests
*/
public abstract class RehashTestBase extends BaseDistFunctionalTest {
protected RehashTestBase() {
cleanup = CleanupPhase.AFTER_METHOD;
tx = true;
performRehashing = true;
}
// this setup has 4 running caches: {c1, c2, c3, c4}
/**
* This is overridden by subclasses. Could typically be a JOIN or LEAVE event.
* @param offline
*/
abstract void performRehashEvent(boolean offline);
/**
* Blocks until a rehash completes.
*/
abstract void waitForRehashCompletion();
void additionalWait() {
TestingUtil.sleepThread(1000);
}
protected List<MagicKey> init() {
List<MagicKey> keys = new ArrayList<MagicKey>(Arrays.asList(
new MagicKey(c1, "k1"), new MagicKey(c2, "k2"),
new MagicKey(c3, "k3"), new MagicKey(c4, "k4")
));
assertEquals(caches.size(), keys.size(), "Received caches" + caches);
int i = 0;
for (Cache<Object, String> c : caches) c.put(keys.get(i++), "v" + i);
i = 0;
for (MagicKey key : keys) assertOwnershipAndNonOwnership(key, false);
log.infof("Initialized with keys %s", keys);
return keys;
}
/**
* Simple test. Put some state, trigger event, test results
*/
@Test
public void testNonTransactional() {
List<MagicKey> keys = init();
log.info("Invoking rehash event");
performRehashEvent(false);
waitForRehashCompletion();
log.info("Rehash complete");
int i = 0;
for (MagicKey key : keys) assertOnAllCachesAndOwnership(key, "v" + ++i);
assertProperConsistentHashOnAllCaches();
}
/**
* More complex - init some state. Start a new transaction, and midway trigger a rehash. Then complete transaction
* and test results.
*/
@Test
public void testTransactional() throws Exception {
final List<MagicKey> keys = init();
final CountDownLatch l = new CountDownLatch(1);
final AtomicBoolean rollback = new AtomicBoolean(false);
Thread th = new Thread("Updater") {
@Override
public void run() {
try {
// start a transaction on c1.
TransactionManager t1 = TestingUtil.getTransactionManager(c1);
t1.begin();
c1.put(keys.get(0), "transactionally_replaced");
Transaction tx = t1.getTransaction();
tx.enlistResource(new XAResourceAdapter() {
public int prepare(Xid id) {
// this would be called *after* the cache prepares.
try {
l.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return XAResource.XA_OK;
}
});
t1.commit();
} catch (Exception e) {
log.error("Error committing transaction", e);
rollback.set(true);
throw new RuntimeException(e);
}
}
};
th.start();
log.info("Invoking rehash event");
performRehashEvent(true);
l.countDown();
th.join();
//ownership can only be verified after the rehashing has completed
waitForRehashCompletion();
log.info("Rehash complete");
//only check for these values if tx was not rolled back
if (!rollback.get()) {
// the ownership of k1 might change during the tx and a cache might end up with it in L1
assertOwnershipAndNonOwnership(keys.get(0), true);
assertOwnershipAndNonOwnership(keys.get(1), l1OnRehash);
assertOwnershipAndNonOwnership(keys.get(2), l1OnRehash);
assertOwnershipAndNonOwnership(keys.get(3), l1OnRehash);
// checking the values will bring the keys to L1, so we want to do it after checking ownership
assertOnAllCaches(keys.get(0), "transactionally_replaced");
assertOnAllCaches(keys.get(1), "v" + 2);
assertOnAllCaches(keys.get(2), "v" + 3);
assertOnAllCaches(keys.get(3), "v" + 4);
assertProperConsistentHashOnAllCaches();
}
}
/**
* A stress test. One node is constantly modified while a rehash occurs.
*/
@Test(enabled = false, description = "Enable after releasing Beta1")
public void testNonTransactionalStress() throws Exception {
stressTest(false);
}
/**
* A stress test. One node is constantly modified using transactions while a rehash occurs.
*/
@Test(enabled = false, description = "Enable after releasing Beta1")
public void testTransactionalStress() throws Exception {
stressTest(true);
}
private void stressTest(boolean tx) throws Exception {
final List<MagicKey> keys = init();
final CountDownLatch latch = new CountDownLatch(1);
List<Updater> updaters = new ArrayList<Updater>(keys.size());
for (MagicKey k : keys) {
Updater u = new Updater(c1, k, latch, tx);
u.start();
updaters.add(u);
}
latch.countDown();
log.info("Invoking rehash event");
performRehashEvent(false);
for (Updater u : updaters) u.complete();
for (Updater u : updaters) u.join();
waitForRehashCompletion();
log.info("Rehash complete");
int i = 0;
for (MagicKey key : keys) assertOnAllCachesAndOwnership(key, "v" + updaters.get(i++).currentValue);
assertProperConsistentHashOnAllCaches();
}
}
class Updater extends Thread {
static final Random r = new Random();
volatile int currentValue = 0;
MagicKey key;
Cache cache;
CountDownLatch latch;
volatile boolean running = true;
TransactionManager tm;
Updater(Cache cache, MagicKey key, CountDownLatch latch, boolean tx) {
super("Updater-" + key);
this.key = key;
this.cache = cache;
this.latch = latch;
if (tx) tm = TestingUtil.getTransactionManager(cache);
}
public void complete() {
running = false;
}
@Override
public void run() {
while (running) {
try {
currentValue++;
if (tm != null) tm.begin();
cache.put(key, "v" + currentValue);
if (tm != null) tm.commit();
TestingUtil.sleepThread(r.nextInt(10) * 10);
} catch (Exception e) {
// do nothing?
}
}
}
}